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