QGIS API Documentation  3.17.0-Master (df2c9ff931)
qgslayoutitemnodeitem.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutitemnodeitem.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 "qgslayoutitemnodeitem.h"
18 #include "qgssymbollayerutils.h"
19 #include "qgssymbol.h"
20 #include "qgsmapsettings.h"
21 #include "qgslayout.h"
22 #include "qgslayoututils.h"
23 #include <limits>
24 #include <cmath>
25 #include <QStyleOptionGraphicsItem>
26 
27 void QgsLayoutNodesItem::setNodes( const QPolygonF &nodes )
28 {
29  mPolygon = nodes;
31  emit clipPathChanged();
32 }
33 
35 {
36  return mCurrentRectangle;
37 }
38 
40 {
41  return mMaxSymbolBleed;
42 }
43 
45  : QgsLayoutItem( layout )
46 {
47  init();
48 }
49 
50 QgsLayoutNodesItem::QgsLayoutNodesItem( const QPolygonF &polygon,
51  QgsLayout *layout )
52  : QgsLayoutItem( layout )
53 {
54  init();
55 
56  const QRectF boundingRect = polygon.boundingRect();
57  attemptSetSceneRect( boundingRect );
58 
59  const QPointF topLeft = boundingRect.topLeft();
60  mPolygon = polygon.translated( -topLeft );
61 }
62 
63 void QgsLayoutNodesItem::init()
64 {
65  // no cache - the node based items cannot reliably determine their real bounds (e.g. due to mitred corners).
66  // this blocks use of the pixmap based cache for these
67  setCacheMode( QGraphicsItem::NoCache );
68  setBackgroundEnabled( false );
69  setFrameEnabled( false );
70 
72 }
73 
75 {
76  QPainter *painter = context.renderContext().painter();
77  painter->setPen( Qt::NoPen );
78  painter->setBrush( Qt::NoBrush );
79 
80  context.renderContext().setForceVectorOutput( true );
82  _draw( context );
83 
84  if ( mDrawNodes && layout()->renderContext().isPreviewRender() )
85  drawNodes( context );
86 }
87 
89  QPointF pt2 ) const
90 {
91  return std::sqrt( std::pow( pt1.x() - pt2.x(), 2 ) + std::pow( pt1.y() - pt2.y(), 2 ) );
92 }
93 
94 bool QgsLayoutNodesItem::addNode( QPointF pt,
95  const bool checkArea,
96  const double radius )
97 {
98  const QPointF start = mapFromScene( pt );
99  double minDistance = std::numeric_limits<double>::max();
100  double maxDistance = ( checkArea ) ? radius : minDistance;
101  bool rc = false;
102  int idx = -1;
103 
104  for ( int i = 0; i != mPolygon.size(); i++ )
105  {
106  // get nodes of polyline
107  const QPointF pt1 = mPolygon.at( i );
108  QPointF pt2 = mPolygon.at( 0 );
109  if ( ( i + 1 ) != mPolygon.size() )
110  pt2 = mPolygon.at( i + 1 );
111 
112  // compute line eq
113  const double coef = ( pt2.y() - pt1.y() ) / ( pt2.x() - pt1.x() );
114  const double b = pt1.y() - coef * pt1.x();
115 
116  double distance = std::numeric_limits<double>::max();
117  if ( std::isinf( coef ) )
118  distance = std::fabs( pt1.x() - start.x() );
119  else
120  {
121  const double coef2 = ( -1 / coef );
122  const double b2 = start.y() - coef2 * start.x();
123 
124  QPointF inter;
125  if ( std::isinf( coef2 ) )
126  {
127  distance = std::fabs( pt1.y() - start.y() );
128  inter.setX( start.x() );
129  inter.setY( pt1.y() );
130  }
131  else
132  {
133  const double interx = ( b - b2 ) / ( coef2 - coef );
134  const double intery = interx * coef2 + b2;
135  inter.setX( interx );
136  inter.setY( intery );
137  }
138 
139  // check if intersection is within the line
140  const double length1 = computeDistance( inter, pt1 );
141  const double length2 = computeDistance( inter, pt2 );
142  const double length3 = computeDistance( pt1, pt2 );
143  const double length4 = length1 + length2;
144 
145  if ( std::fabs( length3 - length4 ) < std::numeric_limits<float>::epsilon() )
146  distance = computeDistance( inter, start );
147  }
148 
149  if ( distance < minDistance && distance < maxDistance )
150  {
151  minDistance = distance;
152  idx = i;
153  }
154  }
155 
156  if ( idx >= 0 )
157  {
158  rc = _addNode( idx, start, maxDistance );
159  updateSceneRect();
160  emit clipPathChanged();
161  }
162 
163  return rc;
164 }
165 
166 void QgsLayoutNodesItem::drawNodes( QgsLayoutItemRenderContext &context ) const
167 {
168  context.renderContext().painter()->setRenderHint( QPainter::Antialiasing, false );
169 
170  double rectSize = 9.0 / context.viewScaleFactor();
171 
172  QVariantMap properties;
173  properties.insert( QStringLiteral( "name" ), QStringLiteral( "cross" ) );
174  properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "red" ) );
175 
176  std::unique_ptr<QgsMarkerSymbol> symbol;
177  symbol.reset( QgsMarkerSymbol::createSimple( properties ) );
178  symbol->setSize( rectSize );
179  symbol->setAngle( 45 );
180 
181  symbol->startRender( context.renderContext() );
182  for ( QPointF pt : mPolygon )
183  symbol->renderPoint( pt * context.viewScaleFactor(), nullptr, context.renderContext() );
184  symbol->stopRender( context.renderContext() );
185 
186  if ( mSelectedNode >= 0 && mSelectedNode < mPolygon.size() )
187  drawSelectedNode( context );
188 }
189 
190 void QgsLayoutNodesItem::drawSelectedNode( QgsLayoutItemRenderContext &context ) const
191 {
192  double rectSize = 9.0 / context.viewScaleFactor();
193 
194  QVariantMap properties;
195  properties.insert( QStringLiteral( "name" ), QStringLiteral( "square" ) );
196  properties.insert( QStringLiteral( "color" ), QStringLiteral( "0, 0, 0, 0" ) );
197  properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "blue" ) );
198  properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "4" ) );
199 
200  std::unique_ptr<QgsMarkerSymbol> symbol;
201  symbol.reset( QgsMarkerSymbol::createSimple( properties ) );
202  symbol->setSize( rectSize );
203 
204  symbol->startRender( context.renderContext() );
205  symbol->renderPoint( mPolygon.at( mSelectedNode ) * context.viewScaleFactor(), nullptr, context.renderContext() );
206  symbol->stopRender( context.renderContext() );
207 }
208 
210  const bool searchInRadius,
211  const double radius ) const
212 {
213  const QPointF pt = mapFromScene( node );
214  double nearestDistance = std::numeric_limits<double>::max();
215  double maxDistance = ( searchInRadius ) ? radius : nearestDistance;
216  double distance = 0;
217  int idx = -1;
218 
219  int i = 0;
220  for ( QPointF polyPt : mPolygon )
221  {
222  distance = computeDistance( pt, polyPt );
223  if ( distance < nearestDistance && distance < maxDistance )
224  {
225  nearestDistance = distance;
226  idx = i;
227  }
228  i++;
229  }
230 
231  return idx;
232 }
233 
234 bool QgsLayoutNodesItem::nodePosition( const int index, QPointF &position ) const
235 {
236  bool rc( false );
237 
238  if ( index >= 0 && index < mPolygon.size() )
239  {
240  position = mapToScene( mPolygon.at( index ) );
241  rc = true;
242  }
243 
244  return rc;
245 }
246 
247 bool QgsLayoutNodesItem::removeNode( const int index )
248 {
249  bool rc = _removeNode( index );
250  if ( rc )
251  {
252  updateSceneRect();
253  emit clipPathChanged();
254  }
255  return rc;
256 }
257 
258 bool QgsLayoutNodesItem::moveNode( const int index, QPointF pt )
259 {
260  bool rc( false );
261 
262  if ( index >= 0 && index < mPolygon.size() )
263  {
264  QPointF nodeItem = mapFromScene( pt );
265  mPolygon.replace( index, nodeItem );
266  updateSceneRect();
267  emit clipPathChanged();
268  rc = true;
269  }
270 
271  return rc;
272 }
273 
274 bool QgsLayoutNodesItem::readPropertiesFromElement( const QDomElement &itemElem,
275  const QDomDocument &, const QgsReadWriteContext &context )
276 {
277  // restore style
278  QDomElement styleSymbolElem = itemElem.firstChildElement( QStringLiteral( "symbol" ) );
279  if ( !styleSymbolElem.isNull() )
280  _readXmlStyle( styleSymbolElem, context );
281 
282  // restore nodes
283  mPolygon.clear();
284  QDomNodeList nodesList = itemElem.elementsByTagName( QStringLiteral( "node" ) );
285  for ( int i = 0; i < nodesList.size(); i++ )
286  {
287  QDomElement nodeElem = nodesList.at( i ).toElement();
288  QPointF newPt;
289  newPt.setX( nodeElem.attribute( QStringLiteral( "x" ) ).toDouble() );
290  newPt.setY( nodeElem.attribute( QStringLiteral( "y" ) ).toDouble() );
291  mPolygon.append( newPt );
292  }
293 
294  emit changed();
295  emit clipPathChanged();
296  return true;
297 }
298 
300 {
301  // get the bounding rect for the polygon currently displayed
302  const QRectF boundingRect = mPolygon.boundingRect();
303 
304  // compute x/y ratio
305  const float ratioX = !qgsDoubleNear( boundingRect.width(), 0.0 )
306  ? rect().width() / boundingRect.width() : 0;
307  const float ratioY = !qgsDoubleNear( boundingRect.height(), 0.0 )
308  ? rect().height() / boundingRect.height() : 0;
309 
310  // scaling
311  QTransform trans;
312  trans = trans.scale( ratioX, ratioY );
313  mPolygon = trans.map( mPolygon );
314  emit clipPathChanged();
315 }
316 
317 bool QgsLayoutNodesItem::setSelectedNode( const int index )
318 {
319  bool rc = false;
320 
321  if ( index >= 0 && index < mPolygon.size() )
322  {
323  mSelectedNode = index;
324  rc = true;
325  }
326 
327  return rc;
328 }
329 
331 {
332  // set the new scene rectangle
333  const QRectF br = mPolygon.boundingRect();
334 
335  const QPointF topLeft = mapToScene( br.topLeft() );
336  //will trigger updateBoundingRect if necessary
337  attemptSetSceneRect( QRectF( topLeft.x(), topLeft.y(), br.width(), br.height() ) );
338 
339  // update polygon position
340  mPolygon.translate( -br.topLeft().x(), -br.topLeft().y() );
341 }
342 
344 {
345  QRectF br = rect();
347  mCurrentRectangle = br;
348 
349  // update
350  prepareGeometryChange();
351  update();
352 }
353 
354 bool QgsLayoutNodesItem::writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
355 {
356  // style
357  _writeXmlStyle( doc, elem, context );
358 
359  // write nodes
360  QDomElement nodesElem = doc.createElement( QStringLiteral( "nodes" ) );
361  for ( QPointF pt : mPolygon )
362  {
363  QDomElement nodeElem = doc.createElement( QStringLiteral( "node" ) );
364  nodeElem.setAttribute( QStringLiteral( "x" ), QString::number( pt.x() ) );
365  nodeElem.setAttribute( QStringLiteral( "y" ), QString::number( pt.y() ) );
366  nodesElem.appendChild( nodeElem );
367  }
368  elem.appendChild( nodesElem );
369 
370  return true;
371 }
virtual void _writeXmlStyle(QDomDocument &doc, QDomElement &elmt, const QgsReadWriteContext &context) const =0
Method called in writeXml.
double computeDistance(QPointF pt1, QPointF pt2) const
Compute an euclidean distance between 2 nodes.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
The class is used as a container of context for various read/write operations on other objects...
double viewScaleFactor() const
Returns the current view zoom (scale factor).
Definition: qgslayoutitem.h:94
void updateSceneRect()
Update the current scene rectangle for this item.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
Base class for graphical items within a QgsLayout.
QRectF mCurrentRectangle
Current bounding rectangle of shape.
virtual void _draw(QgsLayoutItemRenderContext &context, const QStyleOptionGraphicsItem *itemStyle=nullptr)=0
Method called in paint.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316
double mMaxSymbolBleed
Max symbol bleed.
bool nodePosition(int index, QPointF &position) const
Gets the position of a node in scene coordinates.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
virtual bool _removeNode(int nodeIndex)=0
Method called in removeNode.
QPolygonF mPolygon
Shape&#39;s nodes.
virtual void updateBoundingRect()
Called when the bounding rect of the item should recalculated.
int nodeAtPosition(QPointF point, bool searchInRadius=true, double radius=10) const
Search for the nearest node in the shape within a maximal area.
void sizePositionChanged()
Emitted when the item&#39;s size or position changes.
const QgsLayout * layout() const
Returns the layout the object is attached to.
void attemptSetSceneRect(const QRectF &rect, bool includesFrame=false)
Attempts to update the item&#39;s position and size to match the passed rect in layout coordinates...
QgsRenderContext & renderContext()
Returns a reference to the context&#39;s render context.
Definition: qgslayoutitem.h:72
void clipPathChanged()
Emitted when the item&#39;s clipping path has changed.
QgsLayoutNodesItem(QgsLayout *layout)
Constructor for QgsLayoutNodesItem, attached to the specified layout.
QRectF boundingRect() const override
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
void setNodes(const QPolygonF &nodes)
Sets the nodes the shape consists of.
bool setSelectedNode(int index)
Selects a node by index.
void setBackgroundEnabled(bool drawBackground)
Sets whether this item has a background drawn under it or not.
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:44
bool removeNode(int index)
Remove a node with specified index from the shape.
double estimatedFrameBleed() const override
Returns the estimated amount the item&#39;s frame bleeds outside the item&#39;s actual rectangle.
QPainter * painter()
Returns the destination QPainter for the render operation.
QPolygonF nodes() const
Returns the nodes the shape consists of.
virtual void setFrameEnabled(bool drawFrame)
Sets whether this item has a frame drawn around it or not.
static QgsMarkerSymbol * createSimple(const QVariantMap &properties)
Create a marker symbol with one symbol layer: SimpleMarker with specified properties.
Definition: qgssymbol.cpp:1513
bool addNode(QPointF point, bool checkArea=true, double radius=10)
Add a node in current shape.
bool moveNode(int index, QPointF node)
Moves a node to a new position.
void changed()
Emitted when the object&#39;s properties change.
virtual void _readXmlStyle(const QDomElement &elmt, const QgsReadWriteContext &context)=0
Method called in readXml.
virtual bool _addNode(int nodeIndex, QPointF newNode, double radius)=0
Method called in addNode.
void draw(QgsLayoutItemRenderContext &context) override
Draws the item&#39;s contents using the specified item render context.
void rescaleToFitBoundingBox()
Rescale the current shape according to the item&#39;s bounding box.