QGIS API Documentation  3.23.0-Master (22c16f2067)
qgslayoutitemshape.cpp
Go to the documentation of this file.
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 #include "qgsfillsymbol.h"
24 
25 #include <QPainter>
26 
28  : QgsLayoutItem( layout )
29  , mCornerRadius( 0 )
30 {
31  setBackgroundEnabled( false );
32  setFrameEnabled( false );
33  QVariantMap properties;
34  properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
35  properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
36  properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
37  properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "black" ) );
38  properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "0.3" ) );
39  properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
40  mShapeStyleSymbol.reset( QgsFillSymbol::createSimple( properties ) );
41  refreshSymbol( false );
42 
43  connect( this, &QgsLayoutItemShape::sizePositionChanged, this, [ = ]
44  {
45  updateBoundingRect();
46  update();
47  emit clipPathChanged();
48  } );
49 }
50 
52 
54 {
55  return new QgsLayoutItemShape( layout );
56 }
57 
59 {
61 }
62 
64 {
65  switch ( mShape )
66  {
67  case Ellipse:
68  return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemShapeEllipse.svg" ) );
69  case Rectangle:
70  return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemShapeRectangle.svg" ) );
71  case Triangle:
72  return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemShapeTriangle.svg" ) );
73  }
74 
75  return QIcon();
76 }
77 
79 {
80  if ( !id().isEmpty() )
81  {
82  return id();
83  }
84 
85  switch ( mShape )
86  {
87  case Ellipse:
88  return tr( "<Ellipse>" );
89  case Rectangle:
90  return tr( "<Rectangle>" );
91  case Triangle:
92  return tr( "<Triangle>" );
93  }
94 
95  return tr( "<Shape>" );
96 }
97 
98 QgsLayoutItem::Flags QgsLayoutItemShape::itemFlags() const
99 {
100  QgsLayoutItem::Flags flags = QgsLayoutItem::itemFlags();
102  return flags;
103 }
104 
106 {
107  if ( type == mShape )
108  {
109  return;
110  }
111 
112  mShape = type;
113 
114  if ( mLayout && id().isEmpty() )
115  {
116  //notify the model that the display name has changed
117  mLayout->itemsModel()->updateItemDisplayName( this );
118  }
119 
120  emit clipPathChanged();
121 }
122 
123 void QgsLayoutItemShape::refreshSymbol( bool redraw )
124 {
125  if ( auto *lLayout = layout() )
126  {
127  const QgsRenderContext rc = QgsLayoutUtils::createRenderContextForLayout( lLayout, nullptr, lLayout->renderContext().dpi() );
128  mMaxSymbolBleed = ( 25.4 / lLayout->renderContext().dpi() ) * QgsSymbolLayerUtils::estimateMaxSymbolBleed( mShapeStyleSymbol.get(), rc );
129  }
130 
131  updateBoundingRect();
132 
133  if ( redraw )
134  update();
135 
136  emit frameChanged();
137 }
138 
139 void QgsLayoutItemShape::updateBoundingRect()
140 {
141  QRectF rectangle = rect();
142  rectangle.adjust( -mMaxSymbolBleed, -mMaxSymbolBleed, mMaxSymbolBleed, mMaxSymbolBleed );
143  if ( rectangle != mCurrentRectangle )
144  {
145  prepareGeometryChange();
146  mCurrentRectangle = rectangle;
147  }
148 }
149 
151 {
152  if ( !symbol )
153  return;
154 
155  mShapeStyleSymbol.reset( symbol->clone() );
156  refreshSymbol( true );
157 }
158 
160 {
161  mCornerRadius = radius;
162  emit clipPathChanged();
163 }
164 
166 {
167  QPolygonF shapePolygon = mapToScene( calculatePolygon( 1.0 ) );
168  // ensure polygon is closed
169  if ( shapePolygon.at( 0 ) != shapePolygon.constLast() )
170  shapePolygon << shapePolygon.at( 0 );
171  return QgsGeometry::fromQPolygonF( shapePolygon );
172 }
173 
175 {
176  return mCurrentRectangle;
177 }
178 
180 {
181  return mMaxSymbolBleed;
182 }
183 
185 {
186  if ( mShapeStyleSymbol )
187  {
188  QgsStyleSymbolEntity entity( mShapeStyleSymbol.get() );
189  if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, uuid(), displayName() ) ) )
190  return false;
191  }
192 
193  return true;
194 }
195 
197 {
198  QPainter *painter = context.renderContext().painter();
199  painter->setPen( Qt::NoPen );
200  painter->setBrush( Qt::NoBrush );
201 
202  const double scale = context.renderContext().convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
203 
204  const QVector<QPolygonF> rings; //empty list
205 
206  symbol()->startRender( context.renderContext() );
207  symbol()->renderPolygon( calculatePolygon( scale ), &rings, nullptr, context.renderContext() );
208  symbol()->stopRender( context.renderContext() );
209 }
210 
211 QPolygonF QgsLayoutItemShape::calculatePolygon( double scale ) const
212 {
213  QPolygonF shapePolygon;
214 
215  //shapes with curves must be enlarged before conversion to QPolygonF, or
216  //the curves are approximated too much and appear jaggy
217  const QTransform t = QTransform::fromScale( 100, 100 );
218  //inverse transform used to scale created polygons back to expected size
219  const QTransform ti = t.inverted();
220 
221  switch ( mShape )
222  {
223  case Ellipse:
224  {
225  //create an ellipse
226  QPainterPath ellipsePath;
227  ellipsePath.addEllipse( QRectF( 0, 0, rect().width() * scale, rect().height() * scale ) );
228  const QPolygonF ellipsePoly = ellipsePath.toFillPolygon( t );
229  shapePolygon = ti.map( ellipsePoly );
230  break;
231  }
232  case Rectangle:
233  {
234  //if corner radius set, then draw a rounded rectangle
235  if ( mCornerRadius.length() > 0 )
236  {
237  QPainterPath roundedRectPath;
238  const double radius = mLayout->convertToLayoutUnits( mCornerRadius ) * scale;
239  roundedRectPath.addRoundedRect( QRectF( 0, 0, rect().width() * scale, rect().height() * scale ), radius, radius );
240  const QPolygonF roundedPoly = roundedRectPath.toFillPolygon( t );
241  shapePolygon = ti.map( roundedPoly );
242  }
243  else
244  {
245  shapePolygon = QPolygonF( QRectF( 0, 0, rect().width() * scale, rect().height() * scale ) );
246  }
247  break;
248  }
249  case Triangle:
250  {
251  shapePolygon << QPointF( 0, rect().height() * scale );
252  shapePolygon << QPointF( rect().width() * scale, rect().height() * scale );
253  shapePolygon << QPointF( rect().width() / 2.0 * scale, 0 );
254  shapePolygon << QPointF( 0, rect().height() * scale );
255  break;
256  }
257  }
258  return shapePolygon;
259 }
260 
261 bool QgsLayoutItemShape::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
262 {
263  element.setAttribute( QStringLiteral( "shapeType" ), mShape );
264  element.setAttribute( QStringLiteral( "cornerRadiusMeasure" ), mCornerRadius.encodeMeasurement() );
265 
266  const QDomElement shapeStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mShapeStyleSymbol.get(), document, context );
267  element.appendChild( shapeStyleElem );
268 
269  return true;
270 }
271 
272 bool QgsLayoutItemShape::readPropertiesFromElement( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext &context )
273 {
274  mShape = static_cast< Shape >( element.attribute( QStringLiteral( "shapeType" ), QStringLiteral( "0" ) ).toInt() );
275  if ( element.hasAttribute( QStringLiteral( "cornerRadiusMeasure" ) ) )
276  mCornerRadius = QgsLayoutMeasurement::decodeMeasurement( element.attribute( QStringLiteral( "cornerRadiusMeasure" ), QStringLiteral( "0" ) ) );
277  else
278  mCornerRadius = QgsLayoutMeasurement( element.attribute( QStringLiteral( "cornerRadius" ), QStringLiteral( "0" ) ).toDouble() );
279 
280  const QDomElement shapeStyleSymbolElem = element.firstChildElement( QStringLiteral( "symbol" ) );
281  if ( !shapeStyleSymbolElem.isNull() )
282  {
283  mShapeStyleSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( shapeStyleSymbolElem, context ) );
284  refreshSymbol( false );
285  }
286 
287  return true;
288 }
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
Definition: qgsfillsymbol.h:30
static QgsFillSymbol * createSimple(const QVariantMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties.
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, const QgsFeature *f, QgsRenderContext &context, int layer=-1, bool selected=false)
Renders the symbol using the given render context.
QgsFillSymbol * clone() const override
Returns a deep copy of this symbol.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:45
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Definition: qgslayoutitem.h:72
Layout item for basic filled shapes (e.g.
QRectF boundingRect() const override
bool readPropertiesFromElement(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
static QgsLayoutItemShape * create(QgsLayout *layout)
Returns a new shape item for the specified layout.
double estimatedFrameBleed() const override
Returns the estimated amount the item's frame bleeds outside the item's actual rectangle.
~QgsLayoutItemShape() override
void setShapeType(QgsLayoutItemShape::Shape type)
Sets the type of shape (e.g.
QString displayName() const override
Gets item display name.
QgsLayoutItemShape(QgsLayout *layout)
Constructor for QgsLayoutItemShape, with the specified parent layout.
QgsGeometry clipPath() const override
Returns the clipping path generated by this item, in layout coordinates.
bool writePropertiesToElement(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
void setSymbol(QgsFillSymbol *symbol)
Sets the fill symbol used to draw the shape.
QIcon icon() const override
Returns the item's icon.
bool accept(QgsStyleEntityVisitorInterface *visitor) const override
Accepts the specified style entity visitor, causing it to visit all style entities associated with th...
QgsFillSymbol * symbol()
Returns the fill symbol used to draw the shape.
int type() const override
@ Ellipse
Ellipse shape.
@ Rectangle
Rectangle shape.
@ Triangle
Triangle shape.
QgsLayoutItem::Flags itemFlags() const override
Returns the item's flags, which indicate how the item behaves.
void draw(QgsLayoutItemRenderContext &context) override
Draws the item's contents using the specified item render context.
void setCornerRadius(QgsLayoutMeasurement radius)
Sets the corner radius for rounded rectangle corners.
Base class for graphical items within a QgsLayout.
virtual void redraw()
Triggers a redraw (update) of the item.
virtual void setFrameEnabled(bool drawFrame)
Sets whether this item has a frame drawn around it or not.
@ FlagProvidesClipPath
Item can act as a clipping path provider (see clipPath())
void sizePositionChanged()
Emitted when the item's size or position changes.
virtual QString uuid() const
Returns the item identification string.
QString id() const
Returns the item's ID name.
void frameChanged()
Emitted if the item's frame style changes.
virtual Flags itemFlags() const
Returns the item's flags, which indicate how the item behaves.
void clipPathChanged()
Emitted when the item's clipping path has changed.
void setBackgroundEnabled(bool drawBackground)
Sets whether this item has a background drawn under it or not.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
static QgsLayoutMeasurement decodeMeasurement(const QString &string)
Decodes a measurement from a string.
QString encodeMeasurement() const
Encodes the layout measurement to a string.
double length() const
Returns the length of the measurement.
const QgsLayout * layout() const
Returns the layout the object is attached to.
QPointer< QgsLayout > mLayout
static QgsRenderContext createRenderContextForLayout(QgsLayout *layout, QPainter *painter, double dpi=-1)
Creates a render context suitable for the specified layout and painter destination.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:51
The class is used as a container of context for various read/write operations on other objects.
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
An interface for classes which can visit style entity (e.g.
virtual bool visit(const QgsStyleEntityVisitorInterface::StyleLeaf &entity)
Called when the visitor will visit a style entity.
A symbol entity for QgsStyle databases.
Definition: qgsstyle.h:1219
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
void stopRender(QgsRenderContext &context)
Ends the rendering process.
Definition: qgssymbol.cpp:516
void startRender(QgsRenderContext &context, const QgsFields &fields=QgsFields())
Begins the rendering process for the symbol.
Definition: qgssymbol.cpp:489
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:169
Contains information relating to the style entity currently being visited.