QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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
18#include "qgssymbol.h"
19#include "qgslayout.h"
20#include "qgsmarkersymbol.h"
22
23#include <limits>
24#include <cmath>
25#include <QStyleOptionGraphicsItem>
26
27void QgsLayoutNodesItem::setNodes( const QPolygonF &nodes )
28{
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
51 QgsLayout *layout )
52 : QgsLayoutItem( layout )
53{
54 init();
55
56 const QRectF boundingRect = polygon.boundingRect();
58
59 const QPointF topLeft = boundingRect.topLeft();
60 mPolygon = polygon.translated( -topLeft );
61}
62
63void 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
95 const bool checkArea,
96 const double radius )
97{
98 const QPointF start = mapFromScene( pt );
99 double minDistance = std::numeric_limits<double>::max();
100 const 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 );
160 emit clipPathChanged();
161 }
162
163 return rc;
164}
165
166void QgsLayoutNodesItem::drawNodes( QgsLayoutItemRenderContext &context ) const
167{
168 context.renderContext().painter()->setRenderHint( QPainter::Antialiasing, false );
169
170 const 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 ( const QPointF pt : std::as_const( 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
190void QgsLayoutNodesItem::drawSelectedNode( QgsLayoutItemRenderContext &context ) const
191{
192 const 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 const double maxDistance = ( searchInRadius ) ? radius : nearestDistance;
216 double distance = 0;
217 int idx = -1;
218
219 int i = 0;
220 for ( const QPointF polyPt : std::as_const( 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
234bool 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
247bool QgsLayoutNodesItem::removeNode( const int index )
248{
249 const bool rc = _removeNode( index );
250 if ( rc )
251 {
253 emit clipPathChanged();
254 }
255 return rc;
256}
257
258bool QgsLayoutNodesItem::moveNode( const int index, QPointF pt )
259{
260 bool rc( false );
261
262 if ( index >= 0 && index < mPolygon.size() )
263 {
264 const QPointF nodeItem = mapFromScene( pt );
265 mPolygon.replace( index, nodeItem );
267 emit clipPathChanged();
268 rc = true;
269 }
270
271 return rc;
272}
273
274bool QgsLayoutNodesItem::readPropertiesFromElement( const QDomElement &itemElem,
275 const QDomDocument &, const QgsReadWriteContext &context )
276{
277 // restore style
278 const QDomElement styleSymbolElem = itemElem.firstChildElement( QStringLiteral( "symbol" ) );
279 if ( !styleSymbolElem.isNull() )
280 _readXmlStyle( styleSymbolElem, context );
281
282 // restore nodes
283 mPolygon.clear();
284 const QDomNodeList nodesList = itemElem.elementsByTagName( QStringLiteral( "node" ) );
285 for ( int i = 0; i < nodesList.size(); i++ )
286 {
287 const 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
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();
348
349 // update
350 prepareGeometryChange();
351 update();
352}
353
354bool 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 ( const QPointF pt : std::as_const( 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}
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:44
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Definition: qgslayoutitem.h:71
double viewScaleFactor() const
Returns the current view zoom (scale factor).
Definition: qgslayoutitem.h:93
Base class for graphical items within a QgsLayout.
virtual void setFrameEnabled(bool drawFrame)
Sets whether this item has a frame drawn around it or not.
void sizePositionChanged()
Emitted when the item's size or position changes.
void clipPathChanged()
Emitted when the item's clipping path has changed.
void attemptSetSceneRect(const QRectF &rect, bool includesFrame=false)
Attempts to update the item's position and size to match the passed rect in layout coordinates.
void setBackgroundEnabled(bool drawBackground)
Sets whether this item has a background drawn under it or not.
virtual void updateBoundingRect()
Called when the bounding rect of the item should recalculated.
virtual bool _removeNode(int nodeIndex)=0
Method called in removeNode.
QPolygonF nodes() const
Returns the nodes the shape consists of.
double mMaxSymbolBleed
Max symbol bleed.
virtual void _writeXmlStyle(QDomDocument &doc, QDomElement &elmt, const QgsReadWriteContext &context) const =0
Method called in writeXml.
QRectF mCurrentRectangle
Current bounding rectangle of shape.
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
bool setSelectedNode(int index)
Selects a node by index.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
void updateSceneRect()
Update the current scene rectangle for this item.
virtual void _readXmlStyle(const QDomElement &elmt, const QgsReadWriteContext &context)=0
Method called in readXml.
bool removeNode(int index)
Remove a node with specified index from the shape.
QRectF boundingRect() const override
double computeDistance(QPointF pt1, QPointF pt2) const
Compute an euclidean distance between 2 nodes.
bool nodePosition(int index, QPointF &position) const
Gets the position of a node in scene coordinates.
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.
double estimatedFrameBleed() const override
Returns the estimated amount the item's frame bleeds outside the item's actual rectangle.
void draw(QgsLayoutItemRenderContext &context) override
Draws the item's contents using the specified item render context.
virtual void _draw(QgsLayoutItemRenderContext &context, const QStyleOptionGraphicsItem *itemStyle=nullptr)=0
Method called in paint.
QgsLayoutNodesItem(QgsLayout *layout)
Constructor for QgsLayoutNodesItem, attached to the specified layout.
virtual bool _addNode(int nodeIndex, QPointF newNode, double radius)=0
Method called in addNode.
void rescaleToFitBoundingBox()
Rescale the current shape according to the item's bounding box.
void setNodes(const QPolygonF &nodes)
Sets the nodes the shape consists of.
QPolygonF mPolygon
Shape's nodes.
int nodeAtPosition(QPointF point, bool searchInRadius=true, double radius=10) const
Search for the nearest node in the shape within a maximal area.
const QgsLayout * layout() const
Returns the layout the object is attached to.
void changed()
Emitted when the object's properties change.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:50
static QgsMarkerSymbol * createSimple(const QVariantMap &properties)
Create a marker symbol with one symbol layer: SimpleMarker with specified properties.
The class is used as a container of context for various read/write operations on other objects.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
QPainter * painter()
Returns the destination QPainter for the render operation.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3509