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