QGIS API Documentation  3.2.0-Bonn (bc43194)
qgslayoutitempolyline.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutitempolyline.cpp
3  begin : March 2016
4  copyright : (C) 2016 Paul Blottiere, Oslandia
5  email : paul dot blottiere at oslandia dot com
6  ***************************************************************************/
7 
8 /***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
17 #include "qgslayoutitempolyline.h"
18 #include "qgslayoutitemregistry.h"
19 #include "qgssymbollayerutils.h"
20 #include "qgssymbol.h"
21 #include "qgslayout.h"
22 #include "qgsmapsettings.h"
23 #include "qgslayoututils.h"
24 #include "qgsreadwritecontext.h"
25 #include "qgssvgcache.h"
26 #include <QSvgRenderer>
27 #include <limits>
28 
30  : QgsLayoutNodesItem( layout )
31 {
32  createDefaultPolylineStyleSymbol();
33 }
34 
36  : QgsLayoutNodesItem( polyline, layout )
37 {
38  createDefaultPolylineStyleSymbol();
39 }
40 
42 {
43  return new QgsLayoutItemPolyline( layout );
44 }
45 
47 {
49 }
50 
52 {
53  return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemPolyline.svg" ) );
54 }
55 
56 bool QgsLayoutItemPolyline::_addNode( const int indexPoint,
57  QPointF newPoint,
58  const double radius )
59 {
60  const double distStart = computeDistance( newPoint, mPolygon[0] );
61  const double distEnd = computeDistance( newPoint, mPolygon[mPolygon.size() - 1] );
62 
63  if ( indexPoint == ( mPolygon.size() - 1 ) )
64  {
65  if ( distEnd < radius )
66  mPolygon.append( newPoint );
67  else if ( distStart < radius )
68  mPolygon.insert( 0, newPoint );
69  }
70  else
71  mPolygon.insert( indexPoint + 1, newPoint );
72 
73  return true;
74 }
75 
76 bool QgsLayoutItemPolyline::_removeNode( const int index )
77 {
78  if ( index < 0 || index >= mPolygon.size() )
79  return false;
80 
81  mPolygon.remove( index );
82 
83  if ( mPolygon.size() < 2 )
84  mPolygon.clear();
85  else
86  {
87  int newSelectNode = index;
88  if ( index >= mPolygon.size() )
89  newSelectNode = mPolygon.size() - 1;
90  setSelectedNode( newSelectNode );
91  }
92 
93  return true;
94 }
95 
96 void QgsLayoutItemPolyline::createDefaultPolylineStyleSymbol()
97 {
98  QgsStringMap properties;
99  properties.insert( QStringLiteral( "color" ), QStringLiteral( "0,0,0,255" ) );
100  properties.insert( QStringLiteral( "width" ), QStringLiteral( "0.3" ) );
101  properties.insert( QStringLiteral( "capstyle" ), QStringLiteral( "square" ) );
102 
103  mPolylineStyleSymbol.reset( QgsLineSymbol::createSimple( properties ) );
104  refreshSymbol();
105 }
106 
107 void QgsLayoutItemPolyline::refreshSymbol()
108 {
109  if ( layout() )
110  {
111  QgsRenderContext rc = QgsLayoutUtils::createRenderContextForLayout( layout(), nullptr, layout()->renderContext().dpi() );
112  mMaxSymbolBleed = ( 25.4 / layout()->renderContext().dpi() ) * QgsSymbolLayerUtils::estimateMaxSymbolBleed( mPolylineStyleSymbol.get(), rc );
113  }
114 
115  updateSceneRect();
116 
117  emit frameChanged();
118 }
119 
120 void QgsLayoutItemPolyline::drawStartMarker( QPainter *painter )
121 {
122  if ( mPolygon.size() < 2 )
123  return;
124 
125  switch ( mStartMarker )
126  {
127  case MarkerMode::NoMarker:
128  break;
129 
130  case MarkerMode::ArrowHead:
131  {
132  // calculate angle at start of line
133  QLineF startLine( mPolygon.at( 0 ), mPolygon.at( 1 ) );
134  double angle = startLine.angle();
135  drawArrow( painter, mPolygon.at( 0 ), angle );
136  break;
137  }
138 
139  case MarkerMode::SvgMarker:
140  {
141  // calculate angle at start of line
142  QLineF startLine( mPolygon.at( 0 ), mPolygon.at( 1 ) );
143  double angle = startLine.angle();
144  drawSvgMarker( painter, mPolygon.at( 0 ), angle, mStartMarkerFile, mStartArrowHeadHeight );
145  break;
146  }
147  }
148 
149 }
150 
151 void QgsLayoutItemPolyline::drawEndMarker( QPainter *painter )
152 {
153  if ( mPolygon.size() < 2 )
154  return;
155 
156  switch ( mEndMarker )
157  {
158  case MarkerMode::NoMarker:
159  break;
160 
161  case MarkerMode::ArrowHead:
162  {
163  // calculate angle at end of line
164  QLineF endLine( mPolygon.at( mPolygon.count() - 2 ), mPolygon.at( mPolygon.count() - 1 ) );
165  double angle = endLine.angle();
166  drawArrow( painter, endLine.p2(), angle );
167  break;
168  }
169  case MarkerMode::SvgMarker:
170  {
171  // calculate angle at end of line
172  QLineF endLine( mPolygon.at( mPolygon.count() - 2 ), mPolygon.at( mPolygon.count() - 1 ) );
173  double angle = endLine.angle();
174  drawSvgMarker( painter, endLine.p2(), angle, mEndMarkerFile, mEndArrowHeadHeight );
175  break;
176  }
177  }
178 }
179 
180 void QgsLayoutItemPolyline::drawArrow( QPainter *painter, QPointF center, double angle )
181 {
182  // translate angle from ccw from axis to cw from north
183  angle = 90 - angle;
184  QPen p;
185  p.setColor( mArrowHeadStrokeColor );
186  p.setWidthF( mArrowHeadStrokeWidth );
187  painter->setPen( p );
188  QBrush b;
189  b.setColor( mArrowHeadFillColor );
190  painter->setBrush( b );
191  drawArrowHead( painter, center.x(), center.y(), angle, mArrowHeadWidth );
192 }
193 
194 void QgsLayoutItemPolyline::updateMarkerSvgSizes()
195 {
196  setStartSvgMarkerPath( mStartMarkerFile );
197  setEndSvgMarkerPath( mEndMarkerFile );
198 }
199 
200 void QgsLayoutItemPolyline::drawArrowHead( QPainter *p, const double x, const double y, const double angle, const double arrowHeadWidth )
201 {
202  if ( !p )
203  return;
204 
205  double angleRad = angle / 180.0 * M_PI;
206  QPointF middlePoint( x, y );
207  //rotate both arrow points
208  QPointF p1 = QPointF( -arrowHeadWidth / 2.0, arrowHeadWidth );
209  QPointF p2 = QPointF( arrowHeadWidth / 2.0, arrowHeadWidth );
210 
211  QPointF p1Rotated, p2Rotated;
212  p1Rotated.setX( p1.x() * std::cos( angleRad ) + p1.y() * -std::sin( angleRad ) );
213  p1Rotated.setY( p1.x() * std::sin( angleRad ) + p1.y() * std::cos( angleRad ) );
214  p2Rotated.setX( p2.x() * std::cos( angleRad ) + p2.y() * -std::sin( angleRad ) );
215  p2Rotated.setY( p2.x() * std::sin( angleRad ) + p2.y() * std::cos( angleRad ) );
216 
217  QPolygonF arrowHeadPoly;
218  arrowHeadPoly << middlePoint;
219  arrowHeadPoly << QPointF( middlePoint.x() + p1Rotated.x(), middlePoint.y() + p1Rotated.y() );
220  arrowHeadPoly << QPointF( middlePoint.x() + p2Rotated.x(), middlePoint.y() + p2Rotated.y() );
221  QPen arrowPen = p->pen();
222  arrowPen.setJoinStyle( Qt::RoundJoin );
223  QBrush arrowBrush = p->brush();
224  arrowBrush.setStyle( Qt::SolidPattern );
225  p->setPen( arrowPen );
226  p->setBrush( arrowBrush );
227  arrowBrush.setStyle( Qt::SolidPattern );
228  p->drawPolygon( arrowHeadPoly );
229 }
230 
231 void QgsLayoutItemPolyline::drawSvgMarker( QPainter *p, QPointF point, double angle, const QString &markerPath, double height ) const
232 {
233  // translate angle from ccw from axis to cw from north
234  angle = 90 - angle;
235 
236  if ( mArrowHeadWidth <= 0 || height <= 0 )
237  {
238  //bad image size
239  return;
240  }
241 
242  if ( markerPath.isEmpty() )
243  return;
244 
245  QSvgRenderer r;
246  const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( markerPath, mArrowHeadWidth, mArrowHeadFillColor, mArrowHeadStrokeColor, mArrowHeadStrokeWidth,
247  1.0 );
248  r.load( svgContent );
249 
250  p->save();
251  p->translate( point.x(), point.y() );
252  p->rotate( angle );
253  p->translate( -mArrowHeadWidth / 2.0, -height / 2.0 );
254  r.render( p, QRectF( 0, 0, mArrowHeadWidth, height ) );
255  p->restore();
256 }
257 
259 {
260  if ( !id().isEmpty() )
261  return id();
262 
263  return tr( "<Polyline>" );
264 }
265 
266 void QgsLayoutItemPolyline::_draw( QgsLayoutItemRenderContext &context, const QStyleOptionGraphicsItem * )
267 {
268  context.renderContext().painter()->save();
269  //setup painter scaling to dots so that raster symbology is drawn to scale
271  QTransform t = QTransform::fromScale( scale, scale );
272 
273  mPolylineStyleSymbol->startRender( context.renderContext() );
274  mPolylineStyleSymbol->renderPolyline( t.map( mPolygon ), nullptr, context.renderContext() );
275  mPolylineStyleSymbol->stopRender( context.renderContext() );
276 
277  // painter is scaled to dots, so scale back to layout units
278  context.renderContext().painter()->scale( context.renderContext().scaleFactor(), context.renderContext().scaleFactor() );
279 
280  drawStartMarker( context.renderContext().painter() );
281  drawEndMarker( context.renderContext().painter() );
282  context.renderContext().painter()->restore();
283 }
284 
285 void QgsLayoutItemPolyline::_readXmlStyle( const QDomElement &elmt, const QgsReadWriteContext &context )
286 {
287  mPolylineStyleSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsLineSymbol>( elmt, context ) );
288 }
289 
291 {
292  mPolylineStyleSymbol.reset( static_cast<QgsLineSymbol *>( symbol->clone() ) );
293  refreshSymbol();
294 }
295 
297 {
298  mStartMarker = mode;
299  update();
300 }
301 
303 {
304  mEndMarker = mode;
305  update();
306 }
307 
309 {
310  mArrowHeadWidth = width;
311  updateMarkerSvgSizes();
312  update();
313 }
314 
316 {
317  QSvgRenderer r;
318  mStartMarkerFile = path;
319  if ( path.isEmpty() || !r.load( path ) )
320  {
321  mStartArrowHeadHeight = 0;
322  }
323  else
324  {
325  //calculate mArrowHeadHeight from svg file and mArrowHeadWidth
326  QRect viewBox = r.viewBox();
327  mStartArrowHeadHeight = mArrowHeadWidth / viewBox.width() * viewBox.height();
328  }
330 }
331 
333 {
334  QSvgRenderer r;
335  mEndMarkerFile = path;
336  if ( path.isEmpty() || !r.load( path ) )
337  {
338  mEndArrowHeadHeight = 0;
339  }
340  else
341  {
342  //calculate mArrowHeadHeight from svg file and mArrowHeadWidth
343  QRect viewBox = r.viewBox();
344  mEndArrowHeadHeight = mArrowHeadWidth / viewBox.width() * viewBox.height();
345  }
347 }
348 
350 {
351  mArrowHeadStrokeColor = color;
352  update();
353 }
354 
356 {
357  mArrowHeadFillColor = color;
358  update();
359 }
360 
362 {
363  mArrowHeadStrokeWidth = width;
365  update();
366 }
367 
368 void QgsLayoutItemPolyline::_writeXmlStyle( QDomDocument &doc, QDomElement &elmt, const QgsReadWriteContext &context ) const
369 {
370  const QDomElement pe = QgsSymbolLayerUtils::saveSymbol( QString(),
371  mPolylineStyleSymbol.get(),
372  doc,
373  context );
374  elmt.appendChild( pe );
375 }
376 
377 bool QgsLayoutItemPolyline::writePropertiesToElement( QDomElement &elmt, QDomDocument &doc, const QgsReadWriteContext &context ) const
378 {
379  QgsLayoutNodesItem::writePropertiesToElement( elmt, doc, context );
380 
381  // absolute paths to relative
382  QString startMarkerPath = QgsSymbolLayerUtils::svgSymbolPathToName( mStartMarkerFile, context.pathResolver() );
383  QString endMarkerPath = QgsSymbolLayerUtils::svgSymbolPathToName( mEndMarkerFile, context.pathResolver() );
384 
385  elmt.setAttribute( QStringLiteral( "arrowHeadWidth" ), QString::number( mArrowHeadWidth ) );
386  elmt.setAttribute( QStringLiteral( "arrowHeadFillColor" ), QgsSymbolLayerUtils::encodeColor( mArrowHeadFillColor ) );
387  elmt.setAttribute( QStringLiteral( "arrowHeadOutlineColor" ), QgsSymbolLayerUtils::encodeColor( mArrowHeadStrokeColor ) );
388  elmt.setAttribute( QStringLiteral( "outlineWidth" ), QString::number( mArrowHeadStrokeWidth ) );
389  elmt.setAttribute( QStringLiteral( "markerMode" ), mEndMarker );
390  elmt.setAttribute( QStringLiteral( "startMarkerMode" ), mStartMarker );
391  elmt.setAttribute( QStringLiteral( "startMarkerFile" ), startMarkerPath );
392  elmt.setAttribute( QStringLiteral( "endMarkerFile" ), endMarkerPath );
393 
394  return true;
395 }
396 
397 bool QgsLayoutItemPolyline::readPropertiesFromElement( const QDomElement &elmt, const QDomDocument &doc, const QgsReadWriteContext &context )
398 {
399  mArrowHeadWidth = elmt.attribute( QStringLiteral( "arrowHeadWidth" ), QStringLiteral( "2.0" ) ).toDouble();
400  mArrowHeadFillColor = QgsSymbolLayerUtils::decodeColor( elmt.attribute( QStringLiteral( "arrowHeadFillColor" ), QStringLiteral( "0,0,0,255" ) ) );
401  mArrowHeadStrokeColor = QgsSymbolLayerUtils::decodeColor( elmt.attribute( QStringLiteral( "arrowHeadOutlineColor" ), QStringLiteral( "0,0,0,255" ) ) );
402  mArrowHeadStrokeWidth = elmt.attribute( QStringLiteral( "outlineWidth" ), QStringLiteral( "1.0" ) ).toDouble();
403  // relative paths to absolute
404  QString startMarkerPath = elmt.attribute( QStringLiteral( "startMarkerFile" ), QLatin1String( "" ) );
405  QString endMarkerPath = elmt.attribute( QStringLiteral( "endMarkerFile" ), QLatin1String( "" ) );
408  mEndMarker = static_cast< QgsLayoutItemPolyline::MarkerMode >( elmt.attribute( QStringLiteral( "markerMode" ), QStringLiteral( "0" ) ).toInt() );
409  mStartMarker = static_cast< QgsLayoutItemPolyline::MarkerMode >( elmt.attribute( QStringLiteral( "startMarkerMode" ), QStringLiteral( "0" ) ).toInt() );
410 
411  QgsLayoutNodesItem::readPropertiesFromElement( elmt, doc, context );
412 
414  return true;
415 }
416 
418 {
419  QRectF br = rect();
420 
421  double margin = std::max( mMaxSymbolBleed, computeMarkerMargin() );
422  br.adjust( -margin, -margin, margin, margin );
423  mCurrentRectangle = br;
424 
425  // update
426  prepareGeometryChange();
427  update();
428 }
429 
430 
431 double QgsLayoutItemPolyline::computeMarkerMargin() const
432 {
433  double margin = 0;
434 
435  if ( mStartMarker == ArrowHead || mEndMarker == ArrowHead )
436  {
437  margin = mArrowHeadStrokeWidth / 2.0 + mArrowHeadWidth * M_SQRT2;
438  }
439 
440  if ( mStartMarker == SvgMarker )
441  {
442  double startMarkerMargin = std::sqrt( 0.25 * ( mStartArrowHeadHeight * mStartArrowHeadHeight + mArrowHeadWidth * mArrowHeadWidth ) );
443  margin = std::max( startMarkerMargin, margin );
444  }
445 
446  if ( mEndMarker == SvgMarker )
447  {
448  double endMarkerMargin = std::sqrt( 0.25 * ( mEndArrowHeadHeight * mEndArrowHeadHeight + mArrowHeadWidth * mArrowHeadWidth ) );
449  margin = std::max( endMarkerMargin, margin );
450  }
451 
452  return margin;
453 }
double arrowHeadWidth() const
Returns the width of line arrow heads in mm.
double computeDistance(QPointF pt1, QPointF pt2) const
Compute an euclidian distance between 2 nodes.
The class is used as a container of context for various read/write operations on other objects...
static QgsSvgCache * svgCache()
Returns the application&#39;s SVG cache, used for caching SVG images and handling parameter replacement w...
void setStartMarker(MarkerMode mode)
Sets the start marker mode, which controls what marker is drawn at the start of the line...
void updateSceneRect()
Update the current scene rectangle for this item.
bool _removeNode(int nodeIndex) override
Method called in removeNode.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
void setArrowHeadWidth(double width)
Sets the width of line arrow heads in mm.
void _writeXmlStyle(QDomDocument &doc, QDomElement &elmt, const QgsReadWriteContext &context) const override
Method called in writeXml.
QRectF mCurrentRectangle
Current bounding rectangle of shape.
void setArrowHeadFillColor(const QColor &color)
Sets the color used to fill the arrow head.
static QgsLineSymbol * createSimple(const QgsStringMap &properties)
Create a line symbol with one symbol layer: SimpleLine with specified properties. ...
Definition: qgssymbol.cpp:1098
void setStartSvgMarkerPath(const QString &path)
Sets the path to a SVG marker to draw at the start of the line.
MarkerMode
Vertex marker mode.
An abstract layout item that provides generic methods for node based shapes such as polygon or polyli...
QString displayName() const override
Gets item display name.
double mMaxSymbolBleed
Max symbol bleed.
void setEndSvgMarkerPath(const QString &path)
Sets the path to a SVG marker to draw at the end of the line.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QPolygonF mPolygon
Shape&#39;s nodes.
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout&#39;s render context, which stores information relating to the current ...
Definition: qgslayout.cpp:355
QMap< QString, QString > QgsStringMap
Definition: qgis.h:501
bool _addNode(int indexPoint, QPointF newPoint, double radius) override
Method called in addNode.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
Definition: MathUtils.cpp:786
void setSymbol(QgsLineSymbol *symbol)
Sets the symbol used to draw the shape.
static QString encodeColor(const QColor &color)
int type() const override
QgsLineSymbol * symbol()
Returns the line symbol used to draw the shape.
void _readXmlStyle(const QDomElement &elmt, const QgsReadWriteContext &context) override
Method called in readXml.
void setArrowHeadStrokeColor(const QColor &color)
Sets the color used to draw the stroke around the arrow head.
void setEndMarker(MarkerMode mode)
Sets the end marker mode, which controls what marker is drawn at the end of the line.
void frameChanged()
Emitted if the item&#39;s frame style changes.
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol&#39;s name from its path.
void setArrowHeadStrokeWidth(double width)
Sets the pen width in millimeters for the stroke of the arrow head.
Layout item for node based polyline shapes.
QIcon icon() const override
Returns the item&#39;s icon.
const QgsLayout * layout() const
Returns the layout the object is attached to.
double dpi() const
Returns the dpi for outputting the layout.
QgsRenderContext & renderContext()
Returns a reference to the context&#39;s render context.
Definition: qgslayoutitem.h:71
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
static QgsRenderContext createRenderContextForLayout(QgsLayout *layout, QPainter *painter, double dpi=-1)
Creates a render context suitable for the specified layout and painter destination.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
QString id() const
Returns the item&#39;s ID name.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
void _draw(QgsLayoutItemRenderContext &context, const QStyleOptionGraphicsItem *itemStyle=nullptr) override
Method called in paint.
static QgsLayoutItemPolyline * create(QgsLayout *layout)
Returns a new polyline item for the specified layout.
bool setSelectedNode(int index)
Selects a node by index.
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:43
const QgsPathResolver & pathResolver() const
Returns path resolver for conversion between relative and absolute paths.
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
QByteArray svgContent(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, double fixedAspectRatio=0)
Gets SVG content.
static QDomElement saveSymbol(const QString &symbolName, QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
QgsLayoutItemPolyline(QgsLayout *layout)
Constructor for QgsLayoutItemPolyline for the specified layout.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol&#39;s path from its name.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
static QColor decodeColor(const QString &str)
QgsLineSymbol * clone() const override
Gets a deep copy of this symbol.
Definition: qgssymbol.cpp:1748