Skip to content

Commit 77badc0

Browse files
committedJul 28, 2020
QgsLayoutItemShape can provide clip paths
1 parent aa0b36b commit 77badc0

File tree

4 files changed

+86
-9
lines changed

4 files changed

+86
-9
lines changed
 

‎python/core/auto_generated/layout/qgslayoutitemshape.sip.in

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ The caller takes responsibility for deleting the returned object.
4949

5050
virtual QString displayName() const;
5151

52+
virtual QgsLayoutItem::Flags itemFlags() const;
53+
5254

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

98+
virtual QgsGeometry clipPath() const;
99+
100+
96101
virtual QRectF boundingRect() const;
97102

98103

‎src/core/layout/qgslayoutitemshape.cpp

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ QgsLayoutItemShape::QgsLayoutItemShape( QgsLayout *layout )
4343
{
4444
updateBoundingRect();
4545
update();
46+
emit clipPathChanged();
4647
} );
4748
}
4849

@@ -91,6 +92,13 @@ QString QgsLayoutItemShape::displayName() const
9192
return tr( "<Shape>" );
9293
}
9394

95+
QgsLayoutItem::Flags QgsLayoutItemShape::itemFlags() const
96+
{
97+
QgsLayoutItem::Flags flags = QgsLayoutItem::itemFlags();
98+
flags |= QgsLayoutItem::FlagProvidesClipPath;
99+
return flags;
100+
}
101+
94102
void QgsLayoutItemShape::setShapeType( QgsLayoutItemShape::Shape type )
95103
{
96104
if ( type == mShape )
@@ -105,6 +113,8 @@ void QgsLayoutItemShape::setShapeType( QgsLayoutItemShape::Shape type )
105113
//notify the model that the display name has changed
106114
mLayout->itemsModel()->updateItemDisplayName( this );
107115
}
116+
117+
emit clipPathChanged();
108118
}
109119

110120
void QgsLayoutItemShape::refreshSymbol()
@@ -141,6 +151,21 @@ void QgsLayoutItemShape::setSymbol( QgsFillSymbol *symbol )
141151
refreshSymbol();
142152
}
143153

154+
void QgsLayoutItemShape::setCornerRadius( QgsLayoutMeasurement radius )
155+
{
156+
mCornerRadius = radius;
157+
emit clipPathChanged();
158+
}
159+
160+
QgsGeometry QgsLayoutItemShape::clipPath() const
161+
{
162+
QPolygonF shapePolygon = mapToScene( calculatePolygon( 1.0 ) );
163+
// ensure polygon is closed
164+
if ( shapePolygon.at( 0 ) != shapePolygon.constLast() )
165+
shapePolygon << shapePolygon.at( 0 );
166+
return QgsGeometry::fromQPolygonF( shapePolygon );
167+
}
168+
144169
QRectF QgsLayoutItemShape::boundingRect() const
145170
{
146171
return mCurrentRectangle;
@@ -169,8 +194,17 @@ void QgsLayoutItemShape::draw( QgsLayoutItemRenderContext &context )
169194
painter->setPen( Qt::NoPen );
170195
painter->setBrush( Qt::NoBrush );
171196

172-
double scale = context.renderContext().convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
197+
const double scale = context.renderContext().convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
198+
199+
QVector<QPolygonF> rings; //empty list
200+
201+
symbol()->startRender( context.renderContext() );
202+
symbol()->renderPolygon( calculatePolygon( scale ), &rings, nullptr, context.renderContext() );
203+
symbol()->stopRender( context.renderContext() );
204+
}
173205

206+
QPolygonF QgsLayoutItemShape::calculatePolygon( double scale ) const
207+
{
174208
QPolygonF shapePolygon;
175209

176210
//shapes with curves must be enlarged before conversion to QPolygonF, or
@@ -216,12 +250,7 @@ void QgsLayoutItemShape::draw( QgsLayoutItemRenderContext &context )
216250
break;
217251
}
218252
}
219-
220-
QVector<QPolygonF> rings; //empty list
221-
222-
symbol()->startRender( context.renderContext() );
223-
symbol()->renderPolygon( shapePolygon, &rings, nullptr, context.renderContext() );
224-
symbol()->stopRender( context.renderContext() );
253+
return shapePolygon;
225254
}
226255

227256
bool QgsLayoutItemShape::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const

‎src/core/layout/qgslayoutitemshape.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class CORE_EXPORT QgsLayoutItemShape : public QgsLayoutItem
6262

6363
//Overridden to return shape type
6464
QString displayName() const override;
65+
QgsLayoutItem::Flags itemFlags() const override;
6566

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

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

104+
QgsGeometry clipPath() const override;
105+
103106
// Depending on the symbol style, the bounding rectangle can be larger than the shape
104107
QRectF boundingRect() const override;
105108

@@ -138,6 +141,8 @@ class CORE_EXPORT QgsLayoutItemShape : public QgsLayoutItem
138141
QRectF mCurrentRectangle;
139142

140143
QgsLayoutMeasurement mCornerRadius;
144+
145+
QPolygonF calculatePolygon( double scale ) const;
141146
};
142147

143148

‎tests/src/python/test_qgslayoutshape.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,16 @@
1313
import qgis # NOQA
1414

1515
from qgis.testing import start_app, unittest
16-
from qgis.core import QgsLayoutItemShape
16+
from qgis.core import (
17+
QgsLayoutItemShape,
18+
QgsProject,
19+
QgsLayout,
20+
QgsLayoutItem,
21+
QgsLayoutMeasurement,
22+
QgsUnitTypes
23+
)
24+
from qgis.PyQt.QtCore import QRectF
25+
from qgis.PyQt.QtTest import QSignalSpy
1726

1827
from test_qgslayoutitem import LayoutItemTestCase
1928

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

38+
def testClipPath(self):
39+
pr = QgsProject()
40+
l = QgsLayout(pr)
41+
shape = QgsLayoutItemShape(l)
42+
43+
shape.setShapeType(QgsLayoutItemShape.Rectangle)
44+
shape.attemptSetSceneRect(QRectF(30, 10, 100, 200))
45+
46+
# must be a closed polygon, in scene coordinates!
47+
self.assertEqual(shape.clipPath().asWkt(), 'Polygon ((30 10, 130 10, 130 210, 30 210, 30 10))')
48+
self.assertTrue(int(shape.itemFlags() & QgsLayoutItem.FlagProvidesClipPath))
49+
50+
spy = QSignalSpy(shape.clipPathChanged)
51+
shape.setCornerRadius(QgsLayoutMeasurement(10, QgsUnitTypes.LayoutMillimeters))
52+
self.assertTrue(shape.clipPath().asWkt(0).startswith('Polygon ((30 20, 30 20, 30 19, 30 19, 30 19, 30 19'))
53+
self.assertEqual(len(spy), 1)
54+
55+
shape.setShapeType(QgsLayoutItemShape.Ellipse)
56+
self.assertEqual(len(spy), 2)
57+
self.assertTrue(shape.clipPath().asWkt(0).startswith('Polygon ((130 110, 130 111, 130 113, 130 114'))
58+
59+
shape.setShapeType(QgsLayoutItemShape.Triangle)
60+
self.assertEqual(len(spy), 3)
61+
self.assertEqual(shape.clipPath().asWkt(), 'Polygon ((30 210, 130 210, 80 10, 30 210))')
62+
63+
shape.attemptSetSceneRect(QRectF(50, 20, 80, 120))
64+
self.assertEqual(len(spy), 4)
65+
self.assertEqual(shape.clipPath().asWkt(), 'Polygon ((50 140, 130 140, 90 20, 50 140))')
66+
2967

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

0 commit comments

Comments
 (0)
Please sign in to comment.