QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 "qgssymbollayerutils.h"
19#include "qgssymbol.h"
20#include "qgsmapsettings.h"
21#include "qgslayout.h"
22#include "qgslayoututils.h"
23#include "qgsmarkersymbol.h"
24
25#include <limits>
26#include <cmath>
27#include <QStyleOptionGraphicsItem>
28
29void QgsLayoutNodesItem::setNodes( const QPolygonF &nodes )
30{
33 emit clipPathChanged();
34}
35
37{
38 return mCurrentRectangle;
39}
40
42{
43 return mMaxSymbolBleed;
44}
45
47 : QgsLayoutItem( layout )
48{
49 init();
50}
51
53 QgsLayout *layout )
54 : QgsLayoutItem( layout )
55{
56 init();
57
58 const QRectF boundingRect = polygon.boundingRect();
60
61 const QPointF topLeft = boundingRect.topLeft();
62 mPolygon = polygon.translated( -topLeft );
63}
64
65void QgsLayoutNodesItem::init()
66{
67 // no cache - the node based items cannot reliably determine their real bounds (e.g. due to mitred corners).
68 // this blocks use of the pixmap based cache for these
69 setCacheMode( QGraphicsItem::NoCache );
70 setBackgroundEnabled( false );
71 setFrameEnabled( false );
72
74}
75
77{
78 QPainter *painter = context.renderContext().painter();
79 painter->setPen( Qt::NoPen );
80 painter->setBrush( Qt::NoBrush );
81
82 context.renderContext().setForceVectorOutput( true );
84 _draw( context );
85
86 if ( mDrawNodes && layout()->renderContext().isPreviewRender() )
87 drawNodes( context );
88}
89
91 QPointF pt2 ) const
92{
93 return std::sqrt( std::pow( pt1.x() - pt2.x(), 2 ) + std::pow( pt1.y() - pt2.y(), 2 ) );
94}
95
97 const bool checkArea,
98 const double radius )
99{
100 const QPointF start = mapFromScene( pt );
101 double minDistance = std::numeric_limits<double>::max();
102 const double maxDistance = ( checkArea ) ? radius : minDistance;
103 bool rc = false;
104 int idx = -1;
105
106 for ( int i = 0; i != mPolygon.size(); i++ )
107 {
108 // get nodes of polyline
109 const QPointF pt1 = mPolygon.at( i );
110 QPointF pt2 = mPolygon.at( 0 );
111 if ( ( i + 1 ) != mPolygon.size() )
112 pt2 = mPolygon.at( i + 1 );
113
114 // compute line eq
115 const double coef = ( pt2.y() - pt1.y() ) / ( pt2.x() - pt1.x() );
116 const double b = pt1.y() - coef * pt1.x();
117
118 double distance = std::numeric_limits<double>::max();
119 if ( std::isinf( coef ) )
120 distance = std::fabs( pt1.x() - start.x() );
121 else
122 {
123 const double coef2 = ( -1 / coef );
124 const double b2 = start.y() - coef2 * start.x();
125
126 QPointF inter;
127 if ( std::isinf( coef2 ) )
128 {
129 distance = std::fabs( pt1.y() - start.y() );
130 inter.setX( start.x() );
131 inter.setY( pt1.y() );
132 }
133 else
134 {
135 const double interx = ( b - b2 ) / ( coef2 - coef );
136 const double intery = interx * coef2 + b2;
137 inter.setX( interx );
138 inter.setY( intery );
139 }
140
141 // check if intersection is within the line
142 const double length1 = computeDistance( inter, pt1 );
143 const double length2 = computeDistance( inter, pt2 );
144 const double length3 = computeDistance( pt1, pt2 );
145 const double length4 = length1 + length2;
146
147 if ( std::fabs( length3 - length4 ) < std::numeric_limits<float>::epsilon() )
148 distance = computeDistance( inter, start );
149 }
150
151 if ( distance < minDistance && distance < maxDistance )
152 {
153 minDistance = distance;
154 idx = i;
155 }
156 }
157
158 if ( idx >= 0 )
159 {
160 rc = _addNode( idx, start, maxDistance );
162 emit clipPathChanged();
163 }
164
165 return rc;
166}
167
168void QgsLayoutNodesItem::drawNodes( QgsLayoutItemRenderContext &context ) const
169{
170 context.renderContext().painter()->setRenderHint( QPainter::Antialiasing, false );
171
172 const double rectSize = 9.0 / context.viewScaleFactor();
173
174 QVariantMap properties;
175 properties.insert( QStringLiteral( "name" ), QStringLiteral( "cross" ) );
176 properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "red" ) );
177
178 std::unique_ptr<QgsMarkerSymbol> symbol;
179 symbol.reset( QgsMarkerSymbol::createSimple( properties ) );
180 symbol->setSize( rectSize );
181 symbol->setAngle( 45 );
182
183 symbol->startRender( context.renderContext() );
184 for ( const QPointF pt : std::as_const( mPolygon ) )
185 symbol->renderPoint( pt * context.viewScaleFactor(), nullptr, context.renderContext() );
186 symbol->stopRender( context.renderContext() );
187
188 if ( mSelectedNode >= 0 && mSelectedNode < mPolygon.size() )
189 drawSelectedNode( context );
190}
191
192void QgsLayoutNodesItem::drawSelectedNode( QgsLayoutItemRenderContext &context ) const
193{
194 const double rectSize = 9.0 / context.viewScaleFactor();
195
196 QVariantMap properties;
197 properties.insert( QStringLiteral( "name" ), QStringLiteral( "square" ) );
198 properties.insert( QStringLiteral( "color" ), QStringLiteral( "0, 0, 0, 0" ) );
199 properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "blue" ) );
200 properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "4" ) );
201
202 std::unique_ptr<QgsMarkerSymbol> symbol;
203 symbol.reset( QgsMarkerSymbol::createSimple( properties ) );
204 symbol->setSize( rectSize );
205
206 symbol->startRender( context.renderContext() );
207 symbol->renderPoint( mPolygon.at( mSelectedNode ) * context.viewScaleFactor(), nullptr, context.renderContext() );
208 symbol->stopRender( context.renderContext() );
209}
210
212 const bool searchInRadius,
213 const double radius ) const
214{
215 const QPointF pt = mapFromScene( node );
216 double nearestDistance = std::numeric_limits<double>::max();
217 const double maxDistance = ( searchInRadius ) ? radius : nearestDistance;
218 double distance = 0;
219 int idx = -1;
220
221 int i = 0;
222 for ( const QPointF polyPt : std::as_const( mPolygon ) )
223 {
224 distance = computeDistance( pt, polyPt );
225 if ( distance < nearestDistance && distance < maxDistance )
226 {
227 nearestDistance = distance;
228 idx = i;
229 }
230 i++;
231 }
232
233 return idx;
234}
235
236bool QgsLayoutNodesItem::nodePosition( const int index, QPointF &position ) const
237{
238 bool rc( false );
239
240 if ( index >= 0 && index < mPolygon.size() )
241 {
242 position = mapToScene( mPolygon.at( index ) );
243 rc = true;
244 }
245
246 return rc;
247}
248
249bool QgsLayoutNodesItem::removeNode( const int index )
250{
251 const bool rc = _removeNode( index );
252 if ( rc )
253 {
255 emit clipPathChanged();
256 }
257 return rc;
258}
259
260bool QgsLayoutNodesItem::moveNode( const int index, QPointF pt )
261{
262 bool rc( false );
263
264 if ( index >= 0 && index < mPolygon.size() )
265 {
266 const QPointF nodeItem = mapFromScene( pt );
267 mPolygon.replace( index, nodeItem );
269 emit clipPathChanged();
270 rc = true;
271 }
272
273 return rc;
274}
275
276bool QgsLayoutNodesItem::readPropertiesFromElement( const QDomElement &itemElem,
277 const QDomDocument &, const QgsReadWriteContext &context )
278{
279 // restore style
280 const QDomElement styleSymbolElem = itemElem.firstChildElement( QStringLiteral( "symbol" ) );
281 if ( !styleSymbolElem.isNull() )
282 _readXmlStyle( styleSymbolElem, context );
283
284 // restore nodes
285 mPolygon.clear();
286 const QDomNodeList nodesList = itemElem.elementsByTagName( QStringLiteral( "node" ) );
287 for ( int i = 0; i < nodesList.size(); i++ )
288 {
289 const QDomElement nodeElem = nodesList.at( i ).toElement();
290 QPointF newPt;
291 newPt.setX( nodeElem.attribute( QStringLiteral( "x" ) ).toDouble() );
292 newPt.setY( nodeElem.attribute( QStringLiteral( "y" ) ).toDouble() );
293 mPolygon.append( newPt );
294 }
295
296 emit changed();
297 emit clipPathChanged();
298 return true;
299}
300
302{
303 // get the bounding rect for the polygon currently displayed
304 const QRectF boundingRect = mPolygon.boundingRect();
305
306 // compute x/y ratio
307 const float ratioX = !qgsDoubleNear( boundingRect.width(), 0.0 )
308 ? rect().width() / boundingRect.width() : 0;
309 const float ratioY = !qgsDoubleNear( boundingRect.height(), 0.0 )
310 ? rect().height() / boundingRect.height() : 0;
311
312 // scaling
313 QTransform trans;
314 trans = trans.scale( ratioX, ratioY );
315 mPolygon = trans.map( mPolygon );
316 emit clipPathChanged();
317}
318
320{
321 bool rc = false;
322
323 if ( index >= 0 && index < mPolygon.size() )
324 {
325 mSelectedNode = index;
326 rc = true;
327 }
328
329 return rc;
330}
331
333{
334 // set the new scene rectangle
335 const QRectF br = mPolygon.boundingRect();
336
337 const QPointF topLeft = mapToScene( br.topLeft() );
338 //will trigger updateBoundingRect if necessary
339 attemptSetSceneRect( QRectF( topLeft.x(), topLeft.y(), br.width(), br.height() ) );
340
341 // update polygon position
342 mPolygon.translate( -br.topLeft().x(), -br.topLeft().y() );
343}
344
346{
347 QRectF br = rect();
350
351 // update
352 prepareGeometryChange();
353 update();
354}
355
356bool QgsLayoutNodesItem::writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
357{
358 // style
359 _writeXmlStyle( doc, elem, context );
360
361 // write nodes
362 QDomElement nodesElem = doc.createElement( QStringLiteral( "nodes" ) );
363 for ( const QPointF pt : std::as_const( mPolygon ) )
364 {
365 QDomElement nodeElem = doc.createElement( QStringLiteral( "node" ) );
366 nodeElem.setAttribute( QStringLiteral( "x" ), QString::number( pt.x() ) );
367 nodeElem.setAttribute( QStringLiteral( "y" ), QString::number( pt.y() ) );
368 nodesElem.appendChild( nodeElem );
369 }
370 elem.appendChild( nodesElem );
371
372 return true;
373}
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:45
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Definition: qgslayoutitem.h:72
double viewScaleFactor() const
Returns the current view zoom (scale factor).
Definition: qgslayoutitem.h:94
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:51
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:2527