Skip to content

Commit

Permalink
QgsLayoutItemPolygon can provide a clip path
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jul 28, 2020
1 parent 2b28395 commit aa0b36b
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 6 deletions.
Expand Up @@ -9,7 +9,6 @@




class QgsLayoutItemPolygon: QgsLayoutNodesItem
{
%Docstring
Expand Down Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion src/core/layout/qgslayoutitemnodeitem.cpp
Expand Up @@ -28,6 +28,7 @@ void QgsLayoutNodesItem::setNodes( const QPolygonF &nodes )
{
mPolygon = nodes;
updateSceneRect();
emit clipPathChanged();
}

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

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

Expand All @@ -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;
}

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

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

Expand All @@ -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 )
Expand Down
16 changes: 16 additions & 0 deletions src/core/layout/qgslayoutitempolygon.cpp
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/core/layout/qgslayoutitempolygon.h
Expand Up @@ -26,7 +26,6 @@
* Layout item for node based polygon shapes.
* \since QGIS 3.0
*/

class CORE_EXPORT QgsLayoutItemPolygon: public QgsLayoutNodesItem
{
Q_OBJECT
Expand Down Expand Up @@ -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.
Expand Down
52 changes: 49 additions & 3 deletions tests/src/python/test_qgslayoutpolygon.py
Expand Up @@ -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
)
Expand Down Expand Up @@ -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.