QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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{
91}
92
94 QPointF pt2 ) const
95{
96 return std::sqrt( std::pow( pt1.x() - pt2.x(), 2 ) + std::pow( pt1.y() - pt2.y(), 2 ) );
97}
98
100 const bool checkArea,
101 const double radius )
102{
103 const QPointF start = mapFromScene( pt );
104 double minDistance = std::numeric_limits<double>::max();
105 const double maxDistance = ( checkArea ) ? radius : minDistance;
106 bool rc = false;
107 int idx = -1;
108
109 for ( int i = 0; i != mPolygon.size(); i++ )
110 {
111 // get nodes of polyline
112 const QPointF pt1 = mPolygon.at( i );
113 QPointF pt2 = mPolygon.at( 0 );
114 if ( ( i + 1 ) != mPolygon.size() )
115 pt2 = mPolygon.at( i + 1 );
116
117 // compute line eq
118 const double coef = ( pt2.y() - pt1.y() ) / ( pt2.x() - pt1.x() );
119 const double b = pt1.y() - coef * pt1.x();
120
121 double distance = std::numeric_limits<double>::max();
122 if ( std::isinf( coef ) )
123 distance = std::fabs( pt1.x() - start.x() );
124 else
125 {
126 const double coef2 = ( -1 / coef );
127 const double b2 = start.y() - coef2 * start.x();
128
129 QPointF inter;
130 if ( std::isinf( coef2 ) )
131 {
132 distance = std::fabs( pt1.y() - start.y() );
133 inter.setX( start.x() );
134 inter.setY( pt1.y() );
135 }
136 else
137 {
138 const double interx = ( b - b2 ) / ( coef2 - coef );
139 const double intery = interx * coef2 + b2;
140 inter.setX( interx );
141 inter.setY( intery );
142 }
143
144 // check if intersection is within the line
145 const double length1 = computeDistance( inter, pt1 );
146 const double length2 = computeDistance( inter, pt2 );
147 const double length3 = computeDistance( pt1, pt2 );
148 const double length4 = length1 + length2;
149
150 if ( std::fabs( length3 - length4 ) < std::numeric_limits<float>::epsilon() )
151 distance = computeDistance( inter, start );
152 }
153
154 if ( distance < minDistance && distance < maxDistance )
155 {
156 minDistance = distance;
157 idx = i;
158 }
159 }
160
161 if ( idx >= 0 )
162 {
163 rc = _addNode( idx, start, maxDistance );
165 emit clipPathChanged();
166 }
167
168 return rc;
169}
170
171void QgsLayoutNodesItem::drawNodes( QgsLayoutItemRenderContext &context ) const
172{
173 context.renderContext().painter()->setRenderHint( QPainter::Antialiasing, false );
174
175 const double rectSize = 9.0 / context.viewScaleFactor();
176
177 QVariantMap properties;
178 properties.insert( QStringLiteral( "name" ), QStringLiteral( "cross" ) );
179 properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "red" ) );
180
181 std::unique_ptr<QgsMarkerSymbol> symbol;
182 symbol.reset( QgsMarkerSymbol::createSimple( properties ) );
183 symbol->setSize( rectSize );
184 symbol->setAngle( 45 );
185
186 symbol->startRender( context.renderContext() );
187 for ( const QPointF pt : std::as_const( mPolygon ) )
188 symbol->renderPoint( pt * context.viewScaleFactor(), nullptr, context.renderContext() );
189 symbol->stopRender( context.renderContext() );
190
191 if ( mSelectedNode >= 0 && mSelectedNode < mPolygon.size() )
192 drawSelectedNode( context );
193}
194
195void QgsLayoutNodesItem::drawSelectedNode( QgsLayoutItemRenderContext &context ) const
196{
197 const double rectSize = 9.0 / context.viewScaleFactor();
198
199 QVariantMap properties;
200 properties.insert( QStringLiteral( "name" ), QStringLiteral( "square" ) );
201 properties.insert( QStringLiteral( "color" ), QStringLiteral( "0, 0, 0, 0" ) );
202 properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "blue" ) );
203 properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "4" ) );
204
205 std::unique_ptr<QgsMarkerSymbol> symbol;
206 symbol.reset( QgsMarkerSymbol::createSimple( properties ) );
207 symbol->setSize( rectSize );
208
209 symbol->startRender( context.renderContext() );
210 symbol->renderPoint( mPolygon.at( mSelectedNode ) * context.viewScaleFactor(), nullptr, context.renderContext() );
211 symbol->stopRender( context.renderContext() );
212}
213
215 const bool searchInRadius,
216 const double radius ) const
217{
218 const QPointF pt = mapFromScene( node );
219 double nearestDistance = std::numeric_limits<double>::max();
220 const double maxDistance = ( searchInRadius ) ? radius : nearestDistance;
221 double distance = 0;
222 int idx = -1;
223
224 int i = 0;
225 for ( const QPointF polyPt : std::as_const( mPolygon ) )
226 {
227 distance = computeDistance( pt, polyPt );
228 if ( distance < nearestDistance && distance < maxDistance )
229 {
230 nearestDistance = distance;
231 idx = i;
232 }
233 i++;
234 }
235
236 return idx;
237}
238
239bool QgsLayoutNodesItem::nodePosition( const int index, QPointF &position ) const
240{
241 bool rc( false );
242
243 if ( index >= 0 && index < mPolygon.size() )
244 {
245 position = mapToScene( mPolygon.at( index ) );
246 rc = true;
247 }
248
249 return rc;
250}
251
252bool QgsLayoutNodesItem::removeNode( const int index )
253{
254 const bool rc = _removeNode( index );
255 if ( rc )
256 {
258 emit clipPathChanged();
259 }
260 return rc;
261}
262
263bool QgsLayoutNodesItem::moveNode( const int index, QPointF pt )
264{
265 bool rc( false );
266
267 if ( index >= 0 && index < mPolygon.size() )
268 {
269 const QPointF nodeItem = mapFromScene( pt );
270 mPolygon.replace( index, nodeItem );
272 emit clipPathChanged();
273 rc = true;
274 }
275
276 return rc;
277}
278
279bool QgsLayoutNodesItem::readPropertiesFromElement( const QDomElement &itemElem,
280 const QDomDocument &, const QgsReadWriteContext &context )
281{
282 // restore style
283 const QDomElement styleSymbolElem = itemElem.firstChildElement( QStringLiteral( "symbol" ) );
284 if ( !styleSymbolElem.isNull() )
285 _readXmlStyle( styleSymbolElem, context );
286
287 // restore nodes
288 mPolygon.clear();
289 const QDomNodeList nodesList = itemElem.elementsByTagName( QStringLiteral( "node" ) );
290 for ( int i = 0; i < nodesList.size(); i++ )
291 {
292 const QDomElement nodeElem = nodesList.at( i ).toElement();
293 QPointF newPt;
294 newPt.setX( nodeElem.attribute( QStringLiteral( "x" ) ).toDouble() );
295 newPt.setY( nodeElem.attribute( QStringLiteral( "y" ) ).toDouble() );
296 mPolygon.append( newPt );
297 }
298
299 emit changed();
300 emit clipPathChanged();
301 return true;
302}
303
305{
306 // get the bounding rect for the polygon currently displayed
307 const QRectF boundingRect = mPolygon.boundingRect();
308
309 // compute x/y ratio
310 const float ratioX = !qgsDoubleNear( boundingRect.width(), 0.0 )
311 ? rect().width() / boundingRect.width() : 0;
312 const float ratioY = !qgsDoubleNear( boundingRect.height(), 0.0 )
313 ? rect().height() / boundingRect.height() : 0;
314
315 // scaling
316 QTransform trans;
317 trans = trans.scale( ratioX, ratioY );
318 mPolygon = trans.map( mPolygon );
319 emit clipPathChanged();
320}
321
323{
324 bool rc = false;
325
326 if ( index >= 0 && index < mPolygon.size() )
327 {
328 mSelectedNode = index;
329 rc = true;
330 }
331
332 return rc;
333}
334
336{
337 // set the new scene rectangle
338 const QRectF br = mPolygon.boundingRect();
339
340 const QPointF topLeft = mapToScene( br.topLeft() );
341 //will trigger updateBoundingRect if necessary
342 attemptSetSceneRect( QRectF( topLeft.x(), topLeft.y(), br.width(), br.height() ) );
343
344 // update polygon position
345 mPolygon.translate( -br.topLeft().x(), -br.topLeft().y() );
346}
347
349{
350 QRectF br = rect();
352 prepareGeometryChange();
354
355 // update
356 update();
357}
358
359bool QgsLayoutNodesItem::writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
360{
361 // style
362 _writeXmlStyle( doc, elem, context );
363
364 // write nodes
365 QDomElement nodesElem = doc.createElement( QStringLiteral( "nodes" ) );
366 for ( const QPointF pt : std::as_const( mPolygon ) )
367 {
368 QDomElement nodeElem = doc.createElement( QStringLiteral( "node" ) );
369 nodeElem.setAttribute( QStringLiteral( "x" ), QString::number( pt.x() ) );
370 nodeElem.setAttribute( QStringLiteral( "y" ), QString::number( pt.y() ) );
371 nodesElem.appendChild( nodeElem );
372 }
373 elem.appendChild( nodesElem );
374
375 return true;
376}
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:43
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Definition: qgslayoutitem.h:70
double viewScaleFactor() const
Returns the current view zoom (scale factor).
Definition: qgslayoutitem.h:92
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.
@ FlagDisableSceneCaching
Item should not have QGraphicsItem caching enabled.
void sizePositionChanged()
Emitted when the item's size or position changes.
void clipPathChanged()
Emitted when the item's clipping path has changed.
QFlags< Flag > Flags
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.
QgsLayoutItem::Flags itemFlags() const override
Returns the item's flags, which indicate how the item behaves.
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:49
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:5207