QGIS API Documentation  2.4.0-Chugiak
 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 <QPainter>
21 #include <QSvgRenderer>
22 #include <QVector2D>
23 
24 #include <cmath>
25 
27  : QgsComposerItem( c )
28  , mStartPoint( 0, 0 )
29  , mStopPoint( 0, 0 )
30  , mStartXIdx( 0 )
31  , mStartYIdx( 0 )
32  , mMarkerMode( DefaultMarker )
33  , mArrowColor( QColor( 0, 0, 0 ) )
34  , mBoundsBehaviour( 24 )
35 {
37 }
38 
39 QgsComposerArrow::QgsComposerArrow( const QPointF& startPoint, const QPointF& stopPoint, QgsComposition* c )
40  : QgsComposerItem( c )
41  , mStartPoint( startPoint )
42  , mStopPoint( stopPoint )
43  , mMarkerMode( DefaultMarker )
44  , mArrowColor( QColor( 0, 0, 0 ) )
45  , mBoundsBehaviour( 24 )
46 {
47  mStartXIdx = mStopPoint.x() < mStartPoint.x();
48  mStartYIdx = mStopPoint.y() < mStartPoint.y();
51 }
52 
54 {
55 
56 }
57 
59 {
60  setArrowHeadWidth( 4 );
61  mPen.setColor( QColor( 0, 0, 0 ) );
62  mPen.setWidthF( 1 );
63 
64  //set composer item brush and pen to transparent white by default
65  setPen( QPen( QColor( 255, 255, 255, 0 ) ) );
66  setBrush( QBrush( QColor( 255, 255, 255, 0 ) ) );
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  //draw arrow
81  QPen arrowPen = mPen;
82  if ( mBoundsBehaviour == 22 )
83  {
84  //if arrow was created in versions prior to 2.4, use the old rendering style
85  arrowPen.setCapStyle( Qt::FlatCap );
86  }
87  arrowPen.setColor( mArrowColor );
88  painter->setPen( arrowPen );
89  painter->setBrush( QBrush( mArrowColor ) );
90  painter->drawLine( QPointF( mStartPoint.x() - pos().x(), mStartPoint.y() - pos().y() ), QPointF( mStopPoint.x() - pos().x(), mStopPoint.y() - pos().y() ) );
91 
92  if ( mMarkerMode == DefaultMarker )
93  {
94  drawHardcodedMarker( painter, EndMarker );
95  }
96  else if ( mMarkerMode == SVGMarker )
97  {
100  }
101 
102  drawFrame( painter );
103  if ( isSelected() )
104  {
105  drawSelectionBoxes( painter );
106  }
107 }
108 
109 void QgsComposerArrow::setSceneRect( const QRectF& rectangle )
110 {
111  if ( rectangle.width() < 0 )
112  {
113  mStartXIdx = 1 - mStartXIdx;
114  }
115  if ( rectangle.height() < 0 )
116  {
117  mStartYIdx = 1 - mStartYIdx;
118  }
119 
120  double margin = computeMarkerMargin();
121 
122  // Ensure the rectangle is at least as large as needed to include the markers
123  QRectF rect = rectangle.united( QRectF( rectangle.x(), rectangle.y(), 2. * margin, 2. * margin ) );
124 
125  // Compute new start and stop positions
126  double x[2] = {rect.x(), rect.x() + rect.width()};
127  double y[2] = {rect.y(), rect.y() + rect.height()};
128 
129  double xsign = x[mStartXIdx] < x[1 - mStartXIdx] ? 1.0 : -1.0;
130  double ysign = y[mStartYIdx] < y[1 - mStartYIdx] ? 1.0 : -1.0;
131 
132  mStartPoint = QPointF( x[mStartXIdx] + xsign * margin, y[mStartYIdx] + ysign * margin );
133  mStopPoint = QPointF( x[1 - mStartXIdx] - xsign * margin, y[1 - mStartYIdx] - ysign * margin );
134 
136 }
137 
139 {
140  Q_UNUSED( type );
141  QBrush arrowBrush = p->brush();
142  arrowBrush.setColor( mArrowColor );
143  p->setBrush( arrowBrush );
144  if ( mBoundsBehaviour == 22 )
145  {
146  //if arrow was created in versions prior to 2.4, use the old rendering style
147  drawArrowHead( p, mStopPoint.x() - pos().x(), mStopPoint.y() - pos().y(), angle( mStartPoint, mStopPoint ), mArrowHeadWidth );
148  }
149  else
150  {
151  QVector2D dir = QVector2D( mStopPoint - mStartPoint ).normalized();
152  QPointF stop = mStopPoint + ( dir * 0.5 * mArrowHeadWidth ).toPointF();
153  drawArrowHead( p, stop.x() - pos().x(), stop.y() - pos().y(), angle( mStartPoint, stop ), mArrowHeadWidth );
154  }
155 }
156 
157 void QgsComposerArrow::drawSVGMarker( QPainter* p, MarkerType type, const QString &markerPath )
158 {
159  Q_UNUSED( markerPath );
160  double ang = angle( mStartPoint, mStopPoint );
161 
162  double arrowHeadHeight;
163  if ( type == StartMarker )
164  {
165  arrowHeadHeight = mStartArrowHeadHeight;
166  }
167  else
168  {
169  arrowHeadHeight = mStopArrowHeadHeight;
170  }
171 
172  //prepare paint device
173  int dpi = ( p->device()->logicalDpiX() + p->device()->logicalDpiY() ) / 2;
174  double viewScaleFactor = horizontalViewScaleFactor();
175  int imageWidth = mArrowHeadWidth / 25.4 * dpi;
176  int imageHeight = arrowHeadHeight / 25.4 * dpi;
177 
178  //make nicer preview
180  {
181  imageWidth *= qMin( viewScaleFactor, 10.0 );
182  imageHeight *= qMin( viewScaleFactor, 10.0 );
183  }
184  QImage markerImage( imageWidth, imageHeight, QImage::Format_ARGB32 );
185  QColor markerBG( 255, 255, 255, 0 ); //transparent white background
186  markerImage.fill( markerBG.rgba() );
187 
188  QPointF imageFixPoint;
189  imageFixPoint.setX( mArrowHeadWidth / 2.0 );
190  QPointF canvasPoint;
191  if ( type == StartMarker )
192  {
193  canvasPoint = QPointF( mStartPoint.x() - pos().x(), mStartPoint.y() - pos().y() );
194  imageFixPoint.setY( mStartArrowHeadHeight );
195  }
196  else //end marker
197  {
198  canvasPoint = QPointF( mStopPoint.x() - pos().x(), mStopPoint.y() - pos().y() );
199  imageFixPoint.setY( 0 );
200  }
201 
202  //rasterize svg
203  QSvgRenderer r;
204  if ( type == StartMarker )
205  {
206  if ( mStartMarkerFile.isEmpty() || !r.load( mStartMarkerFile ) )
207  {
208  return;
209  }
210  }
211  else //end marker
212  {
213  if ( mEndMarkerFile.isEmpty() || !r.load( mEndMarkerFile ) )
214  {
215  return;
216  }
217  }
218 
219  QPainter imagePainter( &markerImage );
220  r.render( &imagePainter );
221 
222  p->save();
223  if ( mBoundsBehaviour == 22 )
224  {
225  //if arrow was created in versions prior to 2.4, use the old rendering style
226  //rotate image fix point for backtransform
227  QPointF fixPoint;
228  if ( type == StartMarker )
229  {
230  fixPoint.setX( 0 ); fixPoint.setY( arrowHeadHeight / 2.0 );
231  }
232  else
233  {
234  fixPoint.setX( 0 ); fixPoint.setY( -arrowHeadHeight / 2.0 );
235  }
236  QPointF rotatedFixPoint;
237  double angleRad = ang / 180 * M_PI;
238  rotatedFixPoint.setX( fixPoint.x() * cos( angleRad ) + fixPoint.y() * -sin( angleRad ) );
239  rotatedFixPoint.setY( fixPoint.x() * sin( angleRad ) + fixPoint.y() * cos( angleRad ) );
240  p->translate( canvasPoint.x() - rotatedFixPoint.x() , canvasPoint.y() - rotatedFixPoint.y() );
241  }
242  else
243  {
244  p->translate( canvasPoint.x() , canvasPoint.y() );
245  }
246 
247  p->rotate( ang );
248  p->translate( -mArrowHeadWidth / 2.0, -arrowHeadHeight / 2.0 );
249 
250  p->drawImage( QRectF( 0, 0, mArrowHeadWidth, arrowHeadHeight ), markerImage, QRectF( 0, 0, imageWidth, imageHeight ) );
251  p->restore();
252 
253  return;
254 }
255 
256 void QgsComposerArrow::setStartMarker( const QString& svgPath )
257 {
258  QSvgRenderer r;
259  if ( svgPath.isEmpty() || !r.load( svgPath ) )
260  {
261  return;
262  // mStartArrowHeadHeight = 0;
263  }
264  mStartMarkerFile = svgPath;
265 
266  //calculate mArrowHeadHeight from svg file and mArrowHeadWidth
267  QRect viewBox = r.viewBox();
268  mStartArrowHeadHeight = mArrowHeadWidth / viewBox.width() * viewBox.height();
270 }
271 
272 void QgsComposerArrow::setEndMarker( const QString& svgPath )
273 {
274  QSvgRenderer r;
275  if ( svgPath.isEmpty() || !r.load( svgPath ) )
276  {
277  return;
278  // mStopArrowHeadHeight = 0;
279  }
280  mEndMarkerFile = svgPath;
281 
282  //calculate mArrowHeadHeight from svg file and mArrowHeadWidth
283  QRect viewBox = r.viewBox();
284  mStopArrowHeadHeight = mArrowHeadWidth / viewBox.width() * viewBox.height();
286 }
287 
289 {
290  mPen.setWidthF( width );
292 }
293 
295 {
296  mArrowHeadWidth = width;
300 }
301 
303 {
304  double margin = 0;
305 
306  if ( mBoundsBehaviour == 22 )
307  {
308  //if arrow was created in versions prior to 2.4, use the old rendering style
309  if ( mMarkerMode == DefaultMarker )
310  {
311  margin = mPen.widthF() / 2.0 + mArrowHeadWidth / 2.0;
312  }
313  else if ( mMarkerMode == NoMarker )
314  {
315  margin = mPen.widthF() / 2.0;
316  }
317  else if ( mMarkerMode == SVGMarker )
318  {
319  double maxArrowHeight = qMax( mStartArrowHeadHeight, mStopArrowHeadHeight );
320  margin = mPen.widthF() / 2 + qMax( mArrowHeadWidth / 2.0, maxArrowHeight / 2.0 );
321  }
322  }
323  else
324  {
325  if ( mMarkerMode == DefaultMarker )
326  {
327  margin = mPen.widthF() / std::sqrt( 2.0 ) + mArrowHeadWidth / 2.0;
328  }
329  else if ( mMarkerMode == NoMarker )
330  {
331  margin = mPen.widthF() / std::sqrt( 2.0 );
332  }
333  else if ( mMarkerMode == SVGMarker )
334  {
335  double startMarkerMargin = std::sqrt( 0.25 * ( mStartArrowHeadHeight * mStartArrowHeadHeight + mArrowHeadWidth * mArrowHeadWidth ) );
336  double stopMarkerMargin = std::sqrt( 0.25 * ( mStopArrowHeadHeight * mStopArrowHeadHeight + mArrowHeadWidth * mArrowHeadWidth ) );
337  double markerMargin = qMax( startMarkerMargin, stopMarkerMargin );
338  margin = qMax( mPen.widthF() / std::sqrt( 2.0 ), markerMargin );
339  }
340  }
341  return margin;
342 }
343 
345 {
346  //rectangle containing start and end point
347  QRectF rect = QRectF( qMin( mStartPoint.x(), mStopPoint.x() ), qMin( mStartPoint.y(), mStopPoint.y() ),
348  qAbs( mStopPoint.x() - mStartPoint.x() ), qAbs( mStopPoint.y() - mStartPoint.y() ) );
349  double enlarge = computeMarkerMargin();
350  rect.adjust( -enlarge, -enlarge, enlarge, enlarge );
352 }
353 
355 {
356  mMarkerMode = mode;
358 }
359 
360 bool QgsComposerArrow::writeXML( QDomElement& elem, QDomDocument & doc ) const
361 {
362  QDomElement composerArrowElem = doc.createElement( "ComposerArrow" );
363  composerArrowElem.setAttribute( "outlineWidth", QString::number( outlineWidth() ) );
364  composerArrowElem.setAttribute( "arrowHeadWidth", QString::number( mArrowHeadWidth ) );
365  composerArrowElem.setAttribute( "markerMode", mMarkerMode );
366  composerArrowElem.setAttribute( "startMarkerFile", mStartMarkerFile );
367  composerArrowElem.setAttribute( "endMarkerFile", mEndMarkerFile );
368  composerArrowElem.setAttribute( "boundsBehaviourVersion", QString::number( mBoundsBehaviour ) );
369 
370  //arrow color
371  QDomElement arrowColorElem = doc.createElement( "ArrowColor" );
372  arrowColorElem.setAttribute( "red", mArrowColor.red() );
373  arrowColorElem.setAttribute( "green", mArrowColor.green() );
374  arrowColorElem.setAttribute( "blue", mArrowColor.blue() );
375  arrowColorElem.setAttribute( "alpha", mArrowColor.alpha() );
376  composerArrowElem.appendChild( arrowColorElem );
377 
378  //start point
379  QDomElement startPointElem = doc.createElement( "StartPoint" );
380  startPointElem.setAttribute( "x", QString::number( mStartPoint.x() ) );
381  startPointElem.setAttribute( "y", QString::number( mStartPoint.y() ) );
382  composerArrowElem.appendChild( startPointElem );
383 
384  //stop point
385  QDomElement stopPointElem = doc.createElement( "StopPoint" );
386  stopPointElem.setAttribute( "x", QString::number( mStopPoint.x() ) );
387  stopPointElem.setAttribute( "y", QString::number( mStopPoint.y() ) );
388  composerArrowElem.appendChild( stopPointElem );
389 
390  elem.appendChild( composerArrowElem );
391  return _writeXML( composerArrowElem, doc );
392 }
393 
394 bool QgsComposerArrow::readXML( const QDomElement& itemElem, const QDomDocument& doc )
395 {
396  mArrowHeadWidth = itemElem.attribute( "arrowHeadWidth", "2.0" ).toDouble();
397  mPen.setWidthF( itemElem.attribute( "outlineWidth", "1.0" ).toDouble() );
398  setStartMarker( itemElem.attribute( "startMarkerFile", "" ) );
399  setEndMarker( itemElem.attribute( "endMarkerFile", "" ) );
400  mMarkerMode = QgsComposerArrow::MarkerMode( itemElem.attribute( "markerMode", "0" ).toInt() );
401  //if bounds behaviour version is not set, default to 2.2 behaviour
402  mBoundsBehaviour = itemElem.attribute( "boundsBehaviourVersion", "22" ).toInt();
403 
404  //arrow color
405  QDomNodeList arrowColorList = itemElem.elementsByTagName( "ArrowColor" );
406  if ( arrowColorList.size() > 0 )
407  {
408  QDomElement arrowColorElem = arrowColorList.at( 0 ).toElement();
409  int red = arrowColorElem.attribute( "red", "0" ).toInt();
410  int green = arrowColorElem.attribute( "green", "0" ).toInt();
411  int blue = arrowColorElem.attribute( "blue", "0" ).toInt();
412  int alpha = arrowColorElem.attribute( "alpha", "255" ).toInt();
413  mArrowColor = QColor( red, green, blue, alpha );
414  }
415 
416  //restore general composer item properties
417  //needs to be before start point / stop point because setSceneRect()
418  QDomNodeList composerItemList = itemElem.elementsByTagName( "ComposerItem" );
419  if ( composerItemList.size() > 0 )
420  {
421  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
422  _readXML( composerItemElem, doc );
423  }
424 
425  //start point
426  QDomNodeList startPointList = itemElem.elementsByTagName( "StartPoint" );
427  if ( startPointList.size() > 0 )
428  {
429  QDomElement startPointElem = startPointList.at( 0 ).toElement();
430  mStartPoint.setX( startPointElem.attribute( "x", "0.0" ).toDouble() );
431  mStartPoint.setY( startPointElem.attribute( "y", "0.0" ).toDouble() );
432  }
433 
434  //stop point
435  QDomNodeList stopPointList = itemElem.elementsByTagName( "StopPoint" );
436  if ( stopPointList.size() > 0 )
437  {
438  QDomElement stopPointElem = stopPointList.at( 0 ).toElement();
439  mStopPoint.setX( stopPointElem.attribute( "x", "0.0" ).toDouble() );
440  mStopPoint.setY( stopPointElem.attribute( "y", "0.0" ).toDouble() );
441  }
442 
443  mStartXIdx = mStopPoint.x() < mStartPoint.x();
444  mStartYIdx = mStopPoint.y() < mStartPoint.y();
445 
447  emit itemChanged();
448  return true;
449 }
450 
451 
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)
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
void itemChanged()
Used e.g.
bool _readXML(const QDomElement &itemElem, const QDomDocument &doc)
Reads parameter that are not subclass specific in document.
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)
int mBoundsBehaviour
Indicates QGIS version to mimic bounding box behaviour for.
QgsComposition * mComposition
Graphics scene for map printing.
void setSceneRect(const QRectF &rectangle)
Modifies position of start and endpoint and calls QgsComposerItem::setSceneRect.
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...
double angle(const QPointF &p1, const QPointF &p2) const
Returns angle of the line from p1 to p2 (clockwise, starting at N)
void drawArrowHead(QPainter *p, double x, double y, double angle, double arrowHeadWidth) const
Draws arrowhead.
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