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