Navigation Menu

Skip to content

Commit

Permalink
QgsLayoutItemShape can provide clip paths
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jul 28, 2020
1 parent aa0b36b commit 77badc0
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 9 deletions.
5 changes: 5 additions & 0 deletions python/core/auto_generated/layout/qgslayoutitemshape.sip.in
Expand Up @@ -49,6 +49,8 @@ The caller takes responsibility for deleting the returned object.

virtual QString displayName() const;

virtual QgsLayoutItem::Flags itemFlags() const;


QgsLayoutItemShape::Shape shapeType() const;
%Docstring
Expand Down Expand Up @@ -93,6 +95,9 @@ Returns the corner radius for rounded rectangle corners.
.. seealso:: :py:func:`setCornerRadius`
%End

virtual QgsGeometry clipPath() const;


virtual QRectF boundingRect() const;


Expand Down
43 changes: 36 additions & 7 deletions src/core/layout/qgslayoutitemshape.cpp
Expand Up @@ -43,6 +43,7 @@ QgsLayoutItemShape::QgsLayoutItemShape( QgsLayout *layout )
{
updateBoundingRect();
update();
emit clipPathChanged();
} );
}

Expand Down Expand Up @@ -91,6 +92,13 @@ QString QgsLayoutItemShape::displayName() const
return tr( "<Shape>" );
}

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

void QgsLayoutItemShape::setShapeType( QgsLayoutItemShape::Shape type )
{
if ( type == mShape )
Expand All @@ -105,6 +113,8 @@ void QgsLayoutItemShape::setShapeType( QgsLayoutItemShape::Shape type )
//notify the model that the display name has changed
mLayout->itemsModel()->updateItemDisplayName( this );
}

emit clipPathChanged();
}

void QgsLayoutItemShape::refreshSymbol()
Expand Down Expand Up @@ -141,6 +151,21 @@ void QgsLayoutItemShape::setSymbol( QgsFillSymbol *symbol )
refreshSymbol();
}

void QgsLayoutItemShape::setCornerRadius( QgsLayoutMeasurement radius )
{
mCornerRadius = radius;
emit clipPathChanged();
}

QgsGeometry QgsLayoutItemShape::clipPath() const
{
QPolygonF shapePolygon = mapToScene( calculatePolygon( 1.0 ) );
// ensure polygon is closed
if ( shapePolygon.at( 0 ) != shapePolygon.constLast() )
shapePolygon << shapePolygon.at( 0 );
return QgsGeometry::fromQPolygonF( shapePolygon );
}

QRectF QgsLayoutItemShape::boundingRect() const
{
return mCurrentRectangle;
Expand Down Expand Up @@ -169,8 +194,17 @@ void QgsLayoutItemShape::draw( QgsLayoutItemRenderContext &context )
painter->setPen( Qt::NoPen );
painter->setBrush( Qt::NoBrush );

double scale = context.renderContext().convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
const double scale = context.renderContext().convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );

QVector<QPolygonF> rings; //empty list

symbol()->startRender( context.renderContext() );
symbol()->renderPolygon( calculatePolygon( scale ), &rings, nullptr, context.renderContext() );
symbol()->stopRender( context.renderContext() );
}

QPolygonF QgsLayoutItemShape::calculatePolygon( double scale ) const
{
QPolygonF shapePolygon;

//shapes with curves must be enlarged before conversion to QPolygonF, or
Expand Down Expand Up @@ -216,12 +250,7 @@ void QgsLayoutItemShape::draw( QgsLayoutItemRenderContext &context )
break;
}
}

QVector<QPolygonF> rings; //empty list

symbol()->startRender( context.renderContext() );
symbol()->renderPolygon( shapePolygon, &rings, nullptr, context.renderContext() );
symbol()->stopRender( context.renderContext() );
return shapePolygon;
}

bool QgsLayoutItemShape::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
Expand Down
7 changes: 6 additions & 1 deletion src/core/layout/qgslayoutitemshape.h
Expand Up @@ -62,6 +62,7 @@ class CORE_EXPORT QgsLayoutItemShape : public QgsLayoutItem

//Overridden to return shape type
QString displayName() const override;
QgsLayoutItem::Flags itemFlags() const override;

/**
* Returns the type of shape (e.g. rectangle, ellipse, etc).
Expand Down Expand Up @@ -92,14 +93,16 @@ class CORE_EXPORT QgsLayoutItemShape : public QgsLayoutItem
* Sets the corner \a radius for rounded rectangle corners.
* \see cornerRadius()
*/
void setCornerRadius( QgsLayoutMeasurement radius ) { mCornerRadius = radius; }
void setCornerRadius( QgsLayoutMeasurement radius );

/**
* Returns the corner radius for rounded rectangle corners.
* \see setCornerRadius()
*/
QgsLayoutMeasurement cornerRadius() const { return mCornerRadius; }

QgsGeometry clipPath() const override;

// Depending on the symbol style, the bounding rectangle can be larger than the shape
QRectF boundingRect() const override;

Expand Down Expand Up @@ -138,6 +141,8 @@ class CORE_EXPORT QgsLayoutItemShape : public QgsLayoutItem
QRectF mCurrentRectangle;

QgsLayoutMeasurement mCornerRadius;

QPolygonF calculatePolygon( double scale ) const;
};


Expand Down
40 changes: 39 additions & 1 deletion tests/src/python/test_qgslayoutshape.py
Expand Up @@ -13,7 +13,16 @@
import qgis # NOQA

from qgis.testing import start_app, unittest
from qgis.core import QgsLayoutItemShape
from qgis.core import (
QgsLayoutItemShape,
QgsProject,
QgsLayout,
QgsLayoutItem,
QgsLayoutMeasurement,
QgsUnitTypes
)
from qgis.PyQt.QtCore import QRectF
from qgis.PyQt.QtTest import QSignalSpy

from test_qgslayoutitem import LayoutItemTestCase

Expand All @@ -26,6 +35,35 @@ class TestQgsLayoutShape(unittest.TestCase, LayoutItemTestCase):
def setUpClass(cls):
cls.item_class = QgsLayoutItemShape

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

shape.setShapeType(QgsLayoutItemShape.Rectangle)
shape.attemptSetSceneRect(QRectF(30, 10, 100, 200))

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

spy = QSignalSpy(shape.clipPathChanged)
shape.setCornerRadius(QgsLayoutMeasurement(10, QgsUnitTypes.LayoutMillimeters))
self.assertTrue(shape.clipPath().asWkt(0).startswith('Polygon ((30 20, 30 20, 30 19, 30 19, 30 19, 30 19'))
self.assertEqual(len(spy), 1)

shape.setShapeType(QgsLayoutItemShape.Ellipse)
self.assertEqual(len(spy), 2)
self.assertTrue(shape.clipPath().asWkt(0).startswith('Polygon ((130 110, 130 111, 130 113, 130 114'))

shape.setShapeType(QgsLayoutItemShape.Triangle)
self.assertEqual(len(spy), 3)
self.assertEqual(shape.clipPath().asWkt(), 'Polygon ((30 210, 130 210, 80 10, 30 210))')

shape.attemptSetSceneRect(QRectF(50, 20, 80, 120))
self.assertEqual(len(spy), 4)
self.assertEqual(shape.clipPath().asWkt(), 'Polygon ((50 140, 130 140, 90 20, 50 140))')


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

0 comments on commit 77badc0

Please sign in to comment.