Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgslayoutitemshape.cpp
3 : : -----------------------
4 : : begin : July 2017
5 : : copyright : (C) 2017 by Nyall Dawson
6 : : email : nyall dot dawson at gmail dot com
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 "qgslayoutitemshape.h"
18 : : #include "qgslayout.h"
19 : : #include "qgslayoututils.h"
20 : : #include "qgssymbollayerutils.h"
21 : : #include "qgslayoutmodel.h"
22 : : #include "qgsstyleentityvisitor.h"
23 : :
24 : : #include <QPainter>
25 : :
26 : 0 : QgsLayoutItemShape::QgsLayoutItemShape( QgsLayout *layout )
27 : 0 : : QgsLayoutItem( layout )
28 : 0 : , mCornerRadius( 0 )
29 : 0 : {
30 : 0 : setBackgroundEnabled( false );
31 : 0 : setFrameEnabled( false );
32 : 0 : QVariantMap properties;
33 : 0 : properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
34 : 0 : properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
35 : 0 : properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
36 : 0 : properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "black" ) );
37 : 0 : properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "0.3" ) );
38 : 0 : properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
39 : 0 : mShapeStyleSymbol.reset( QgsFillSymbol::createSimple( properties ) );
40 : 0 : refreshSymbol();
41 : :
42 : 0 : connect( this, &QgsLayoutItemShape::sizePositionChanged, this, [ = ]
43 : : {
44 : 0 : updateBoundingRect();
45 : 0 : update();
46 : 0 : emit clipPathChanged();
47 : 0 : } );
48 : 0 : }
49 : :
50 : 0 : QgsLayoutItemShape *QgsLayoutItemShape::create( QgsLayout *layout )
51 : : {
52 : 0 : return new QgsLayoutItemShape( layout );
53 : 0 : }
54 : :
55 : 0 : int QgsLayoutItemShape::type() const
56 : : {
57 : 0 : return QgsLayoutItemRegistry::LayoutShape;
58 : : }
59 : :
60 : 0 : QIcon QgsLayoutItemShape::icon() const
61 : : {
62 : 0 : switch ( mShape )
63 : : {
64 : : case Ellipse:
65 : 0 : return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemShapeEllipse.svg" ) );
66 : : case Rectangle:
67 : 0 : return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemShapeRectangle.svg" ) );
68 : : case Triangle:
69 : 0 : return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemShapeTriangle.svg" ) );
70 : : }
71 : :
72 : 0 : return QIcon();
73 : 0 : }
74 : :
75 : 0 : QString QgsLayoutItemShape::displayName() const
76 : : {
77 : 0 : if ( !id().isEmpty() )
78 : : {
79 : 0 : return id();
80 : : }
81 : :
82 : 0 : switch ( mShape )
83 : : {
84 : : case Ellipse:
85 : 0 : return tr( "<Ellipse>" );
86 : : case Rectangle:
87 : 0 : return tr( "<Rectangle>" );
88 : : case Triangle:
89 : 0 : return tr( "<Triangle>" );
90 : : }
91 : :
92 : 0 : return tr( "<Shape>" );
93 : 0 : }
94 : :
95 : 0 : QgsLayoutItem::Flags QgsLayoutItemShape::itemFlags() const
96 : : {
97 : 0 : QgsLayoutItem::Flags flags = QgsLayoutItem::itemFlags();
98 : 0 : flags |= QgsLayoutItem::FlagProvidesClipPath;
99 : 0 : return flags;
100 : : }
101 : :
102 : 0 : void QgsLayoutItemShape::setShapeType( QgsLayoutItemShape::Shape type )
103 : : {
104 : 0 : if ( type == mShape )
105 : : {
106 : 0 : return;
107 : : }
108 : :
109 : 0 : mShape = type;
110 : :
111 : 0 : if ( mLayout && id().isEmpty() )
112 : : {
113 : : //notify the model that the display name has changed
114 : 0 : mLayout->itemsModel()->updateItemDisplayName( this );
115 : 0 : }
116 : :
117 : 0 : emit clipPathChanged();
118 : 0 : }
119 : :
120 : 0 : void QgsLayoutItemShape::refreshSymbol()
121 : : {
122 : 0 : if ( auto *lLayout = layout() )
123 : : {
124 : 0 : QgsRenderContext rc = QgsLayoutUtils::createRenderContextForLayout( lLayout, nullptr, lLayout->renderContext().dpi() );
125 : 0 : mMaxSymbolBleed = ( 25.4 / lLayout->renderContext().dpi() ) * QgsSymbolLayerUtils::estimateMaxSymbolBleed( mShapeStyleSymbol.get(), rc );
126 : 0 : }
127 : :
128 : 0 : updateBoundingRect();
129 : :
130 : 0 : update();
131 : 0 : emit frameChanged();
132 : 0 : }
133 : :
134 : 0 : void QgsLayoutItemShape::updateBoundingRect()
135 : 0 : {
136 : 0 : QRectF rectangle = rect();
137 : 0 : rectangle.adjust( -mMaxSymbolBleed, -mMaxSymbolBleed, mMaxSymbolBleed, mMaxSymbolBleed );
138 : 0 : if ( rectangle != mCurrentRectangle )
139 : 0 : {
140 : 0 : prepareGeometryChange();
141 : 0 : mCurrentRectangle = rectangle;
142 : 0 : }
143 : 0 : }
144 : :
145 : 0 : void QgsLayoutItemShape::setSymbol( QgsFillSymbol *symbol )
146 : : {
147 : 0 : if ( !symbol )
148 : 0 : return;
149 : :
150 : 0 : mShapeStyleSymbol.reset( symbol->clone() );
151 : 0 : refreshSymbol();
152 : 0 : }
153 : :
154 : 0 : void QgsLayoutItemShape::setCornerRadius( QgsLayoutMeasurement radius )
155 : : {
156 : 0 : mCornerRadius = radius;
157 : 0 : emit clipPathChanged();
158 : 0 : }
159 : :
160 : 0 : QgsGeometry QgsLayoutItemShape::clipPath() const
161 : : {
162 : 0 : QPolygonF shapePolygon = mapToScene( calculatePolygon( 1.0 ) );
163 : : // ensure polygon is closed
164 : 0 : if ( shapePolygon.at( 0 ) != shapePolygon.constLast() )
165 : 0 : shapePolygon << shapePolygon.at( 0 );
166 : 0 : return QgsGeometry::fromQPolygonF( shapePolygon );
167 : 0 : }
168 : :
169 : 0 : QRectF QgsLayoutItemShape::boundingRect() const
170 : : {
171 : 0 : return mCurrentRectangle;
172 : : }
173 : :
174 : 0 : double QgsLayoutItemShape::estimatedFrameBleed() const
175 : : {
176 : 0 : return mMaxSymbolBleed;
177 : : }
178 : :
179 : 0 : bool QgsLayoutItemShape::accept( QgsStyleEntityVisitorInterface *visitor ) const
180 : : {
181 : 0 : if ( mShapeStyleSymbol )
182 : : {
183 : 0 : QgsStyleSymbolEntity entity( mShapeStyleSymbol.get() );
184 : 0 : if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, uuid(), displayName() ) ) )
185 : 0 : return false;
186 : 0 : }
187 : :
188 : 0 : return true;
189 : 0 : }
190 : :
191 : 0 : void QgsLayoutItemShape::draw( QgsLayoutItemRenderContext &context )
192 : : {
193 : 0 : QPainter *painter = context.renderContext().painter();
194 : 0 : painter->setPen( Qt::NoPen );
195 : 0 : painter->setBrush( Qt::NoBrush );
196 : :
197 : 0 : const double scale = context.renderContext().convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
198 : :
199 : 0 : QVector<QPolygonF> rings; //empty list
200 : :
201 : 0 : symbol()->startRender( context.renderContext() );
202 : 0 : symbol()->renderPolygon( calculatePolygon( scale ), &rings, nullptr, context.renderContext() );
203 : 0 : symbol()->stopRender( context.renderContext() );
204 : 0 : }
205 : :
206 : 0 : QPolygonF QgsLayoutItemShape::calculatePolygon( double scale ) const
207 : : {
208 : 0 : QPolygonF shapePolygon;
209 : :
210 : : //shapes with curves must be enlarged before conversion to QPolygonF, or
211 : : //the curves are approximated too much and appear jaggy
212 : 0 : QTransform t = QTransform::fromScale( 100, 100 );
213 : : //inverse transform used to scale created polygons back to expected size
214 : 0 : QTransform ti = t.inverted();
215 : :
216 : 0 : switch ( mShape )
217 : : {
218 : : case Ellipse:
219 : : {
220 : : //create an ellipse
221 : 0 : QPainterPath ellipsePath;
222 : 0 : ellipsePath.addEllipse( QRectF( 0, 0, rect().width() * scale, rect().height() * scale ) );
223 : 0 : QPolygonF ellipsePoly = ellipsePath.toFillPolygon( t );
224 : 0 : shapePolygon = ti.map( ellipsePoly );
225 : : break;
226 : 0 : }
227 : : case Rectangle:
228 : : {
229 : : //if corner radius set, then draw a rounded rectangle
230 : 0 : if ( mCornerRadius.length() > 0 )
231 : : {
232 : 0 : QPainterPath roundedRectPath;
233 : 0 : double radius = mLayout->convertToLayoutUnits( mCornerRadius ) * scale;
234 : 0 : roundedRectPath.addRoundedRect( QRectF( 0, 0, rect().width() * scale, rect().height() * scale ), radius, radius );
235 : 0 : QPolygonF roundedPoly = roundedRectPath.toFillPolygon( t );
236 : 0 : shapePolygon = ti.map( roundedPoly );
237 : 0 : }
238 : : else
239 : : {
240 : 0 : shapePolygon = QPolygonF( QRectF( 0, 0, rect().width() * scale, rect().height() * scale ) );
241 : : }
242 : 0 : break;
243 : : }
244 : : case Triangle:
245 : : {
246 : 0 : shapePolygon << QPointF( 0, rect().height() * scale );
247 : 0 : shapePolygon << QPointF( rect().width() * scale, rect().height() * scale );
248 : 0 : shapePolygon << QPointF( rect().width() / 2.0 * scale, 0 );
249 : 0 : shapePolygon << QPointF( 0, rect().height() * scale );
250 : 0 : break;
251 : : }
252 : : }
253 : 0 : return shapePolygon;
254 : 0 : }
255 : :
256 : 0 : bool QgsLayoutItemShape::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
257 : : {
258 : 0 : element.setAttribute( QStringLiteral( "shapeType" ), mShape );
259 : 0 : element.setAttribute( QStringLiteral( "cornerRadiusMeasure" ), mCornerRadius.encodeMeasurement() );
260 : :
261 : 0 : QDomElement shapeStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mShapeStyleSymbol.get(), document, context );
262 : 0 : element.appendChild( shapeStyleElem );
263 : :
264 : : return true;
265 : 0 : }
266 : :
267 : 0 : bool QgsLayoutItemShape::readPropertiesFromElement( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext &context )
268 : : {
269 : 0 : mShape = static_cast< Shape >( element.attribute( QStringLiteral( "shapeType" ), QStringLiteral( "0" ) ).toInt() );
270 : 0 : if ( element.hasAttribute( QStringLiteral( "cornerRadiusMeasure" ) ) )
271 : 0 : mCornerRadius = QgsLayoutMeasurement::decodeMeasurement( element.attribute( QStringLiteral( "cornerRadiusMeasure" ), QStringLiteral( "0" ) ) );
272 : : else
273 : 0 : mCornerRadius = QgsLayoutMeasurement( element.attribute( QStringLiteral( "cornerRadius" ), QStringLiteral( "0" ) ).toDouble() );
274 : :
275 : 0 : QDomElement shapeStyleSymbolElem = element.firstChildElement( QStringLiteral( "symbol" ) );
276 : 0 : if ( !shapeStyleSymbolElem.isNull() )
277 : : {
278 : 0 : mShapeStyleSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( shapeStyleSymbolElem, context ) );
279 : 0 : }
280 : :
281 : : return true;
282 : 0 : }
|