Skip to content
Permalink
Browse files

QgsLayoutItemPolygon can provide a clip path

  • Loading branch information
nyalldawson committed Jul 28, 2020
1 parent 2b28395 commit aa0b36b27dcf6da4eac8f8836ced17ac2a85368d
@@ -9,7 +9,6 @@




class QgsLayoutItemPolygon: QgsLayoutNodesItem
{
%Docstring
@@ -49,6 +48,10 @@ The caller takes responsibility for deleting the returned object.

virtual bool accept( QgsStyleEntityVisitorInterface *visitor ) const;

virtual QgsLayoutItem::Flags itemFlags() const;

virtual QgsGeometry clipPath() const;


QgsFillSymbol *symbol();
%Docstring
@@ -28,6 +28,7 @@ void QgsLayoutNodesItem::setNodes( const QPolygonF &nodes )
{
mPolygon = nodes;
updateSceneRect();
emit clipPathChanged();
}

QRectF QgsLayoutNodesItem::boundingRect() const
@@ -156,6 +157,7 @@ bool QgsLayoutNodesItem::addNode( QPointF pt,
{
rc = _addNode( idx, start, maxDistance );
updateSceneRect();
emit clipPathChanged();
}

return rc;
@@ -246,7 +248,10 @@ bool QgsLayoutNodesItem::removeNode( const int index )
{
bool rc = _removeNode( index );
if ( rc )
{
updateSceneRect();
emit clipPathChanged();
}
return rc;
}

@@ -259,7 +264,7 @@ bool QgsLayoutNodesItem::moveNode( const int index, QPointF pt )
QPointF nodeItem = mapFromScene( pt );
mPolygon.replace( index, nodeItem );
updateSceneRect();

emit clipPathChanged();
rc = true;
}

@@ -287,6 +292,7 @@ bool QgsLayoutNodesItem::readPropertiesFromElement( const QDomElement &itemElem,
}

emit changed();
emit clipPathChanged();
return true;
}

@@ -305,6 +311,7 @@ void QgsLayoutNodesItem::rescaleToFitBoundingBox()
QTransform trans;
trans = trans.scale( ratioX, ratioY );
mPolygon = trans.map( mPolygon );
emit clipPathChanged();
}

bool QgsLayoutNodesItem::setSelectedNode( const int index )
@@ -111,6 +111,22 @@ bool QgsLayoutItemPolygon::accept( QgsStyleEntityVisitorInterface *visitor ) con
return true;
}

QgsLayoutItem::Flags QgsLayoutItemPolygon::itemFlags() const
{
QgsLayoutItem::Flags flags = QgsLayoutNodesItem::itemFlags();
flags |= QgsLayoutItem::FlagProvidesClipPath;
return flags;
}

QgsGeometry QgsLayoutItemPolygon::clipPath() const
{
QPolygonF path = mapToScene( mPolygon );
// ensure polygon is closed
if ( path.at( 0 ) != path.constLast() )
path << path.at( 0 );
return QgsGeometry::fromQPolygonF( path );
}

void QgsLayoutItemPolygon::_draw( QgsLayoutItemRenderContext &context, const QStyleOptionGraphicsItem * )
{
//setup painter scaling to dots so that raster symbology is drawn to scale
@@ -26,7 +26,6 @@
* Layout item for node based polygon shapes.
* \since QGIS 3.0
*/

class CORE_EXPORT QgsLayoutItemPolygon: public QgsLayoutNodesItem
{
Q_OBJECT
@@ -55,6 +54,8 @@ class CORE_EXPORT QgsLayoutItemPolygon: public QgsLayoutNodesItem
QIcon icon() const override;
QString displayName() const override;
bool accept( QgsStyleEntityVisitorInterface *visitor ) const override;
QgsLayoutItem::Flags itemFlags() const override;
QgsGeometry clipPath() const override;

/**
* Returns the fill symbol used to draw the shape.
@@ -12,16 +12,20 @@

import qgis # NOQA

from qgis.PyQt.QtGui import QPolygonF
from qgis.PyQt.QtCore import QPointF
from qgis.PyQt.QtGui import QPolygonF, QPainter, QImage
from qgis.PyQt.QtCore import QPointF, QRectF
from qgis.PyQt.QtXml import QDomDocument
from qgis.PyQt.QtTest import QSignalSpy

from qgis.core import (QgsLayoutItemPolygon,
QgsLayoutItemRegistry,
QgsLayout,
QgsFillSymbol,
QgsProject,
QgsReadWriteContext)
QgsReadWriteContext,
QgsLayoutItem,
QgsLayoutItemRenderContext,
QgsLayoutUtils)
from qgis.testing import (start_app,
unittest
)
@@ -317,6 +321,48 @@ def testBounds(self):
self.assertEqual(bounds.top(), -3.0)
self.assertEqual(bounds.bottom(), 93.0)

def testClipPath(self):
pr = QgsProject()
l = QgsLayout(pr)

p = QPolygonF()
p.append(QPointF(50.0, 30.0))
p.append(QPointF(100.0, 10.0))
p.append(QPointF(200.0, 100.0))
shape = QgsLayoutItemPolygon(p, l)

# must be a closed polygon, in scene coordinates!
self.assertEqual(shape.clipPath().asWkt(), 'Polygon ((50 30, 100 10, 200 100, 50 30))')
self.assertTrue(int(shape.itemFlags() & QgsLayoutItem.FlagProvidesClipPath))

spy = QSignalSpy(shape.clipPathChanged)
self.assertTrue(shape.addNode(QPointF(150, 110), False))
self.assertEqual(shape.clipPath().asWkt(), 'Polygon ((50 30, 100 10, 200 100, 150 110, 50 30))')
self.assertEqual(len(spy), 1)

shape.removeNode(3)
self.assertEqual(len(spy), 2)
self.assertEqual(shape.clipPath().asWkt(), 'Polygon ((50 30, 100 10, 200 100, 50 30))')

shape.moveNode(2, QPointF(180, 100))
self.assertEqual(len(spy), 3)
self.assertEqual(shape.clipPath().asWkt(), 'Polygon ((50 30, 100 10, 180 100, 50 30))')

shape.setNodes(p)
self.assertEqual(len(spy), 4)
self.assertEqual(shape.clipPath().asWkt(), 'Polygon ((100 40, 150 20, 250 110, 100 40))')

shape.attemptSetSceneRect(QRectF(30, 10, 100, 200))
self.assertEqual(shape.clipPath().asWkt(), 'Polygon ((30 30, 80 10, 180 100, 30 30))')
# bit gross - this needs fixing in the item. It shouldn't rely on a draw operation to update the
# path as a result of a move/resize
im = QImage()
p = QPainter(im)
rc = QgsLayoutUtils.createRenderContextForLayout(l, p)
shape.draw(QgsLayoutItemRenderContext(rc))
p.end()
self.assertEqual(len(spy), 5)


if __name__ == '__main__':
unittest.main()

0 comments on commit aa0b36b

Please sign in to comment.
You can’t perform that action at this time.