QGIS API Documentation  3.23.0-Master (eb871beae0)
qgslayoutitempage.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutitempage.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 "qgslayoutitempage.h"
18 #include "qgslayout.h"
19 #include "qgslayoututils.h"
20 #include "qgspagesizeregistry.h"
21 #include "qgssymbollayerutils.h"
24 #include "qgslayoutundostack.h"
25 #include "qgsstyle.h"
26 #include "qgsstyleentityvisitor.h"
27 #include "qgsfillsymbol.h"
28 
29 #include <QPainter>
30 #include <QStyleOptionGraphicsItem>
31 
33  : QgsLayoutItem( layout, false )
34 {
35  setFlag( QGraphicsItem::ItemIsSelectable, false );
36  setFlag( QGraphicsItem::ItemIsMovable, false );
37  setZValue( QgsLayout::ZPage );
38 
39  connect( this, &QgsLayoutItem::sizePositionChanged, this, [ = ]
40  {
41  mBoundingRect = QRectF();
42  prepareGeometryChange();
43  } );
44 
45  const QFont font;
46  const QFontMetrics fm( font );
47  mMaximumShadowWidth = fm.boundingRect( QStringLiteral( "X" ) ).width();
48 
49  mGrid.reset( new QgsLayoutItemPageGrid( pos().x(), pos().y(), rect().width(), rect().height(), mLayout ) );
50  mGrid->setParentItem( this );
51 
52  createDefaultPageStyleSymbol();
53 }
54 
56 
58 {
59  return new QgsLayoutItemPage( layout );
60 }
61 
63 {
65 }
66 
68 {
69  return QObject::tr( "Page" );
70 }
71 
73 {
74  attemptResize( size );
75 }
76 
77 bool QgsLayoutItemPage::setPageSize( const QString &size, Orientation orientation )
78 {
79  QgsPageSize newSize;
80  if ( QgsApplication::pageSizeRegistry()->decodePageSize( size, newSize ) )
81  {
82  switch ( orientation )
83  {
84  case Portrait:
85  break; // nothing to do
86 
87  case Landscape:
88  {
89  // flip height and width
90  const double x = newSize.size.width();
91  newSize.size.setWidth( newSize.size.height() );
92  newSize.size.setHeight( x );
93  break;
94  }
95  }
96 
97  setPageSize( newSize.size );
98  return true;
99  }
100  else
101  {
102  return false;
103  }
104 }
105 
106 QPageLayout QgsLayoutItemPage::pageLayout() const
107 {
108  QPageLayout pageLayout;
109  pageLayout.setMargins( {0, 0, 0, 0} );
110  pageLayout.setMode( QPageLayout::FullPageMode );
111  const QSizeF size = layout()->renderContext().measurementConverter().convert( pageSize(), QgsUnitTypes::LayoutMillimeters ).toQSizeF();
112 
113  if ( pageSize().width() > pageSize().height() )
114  {
115  pageLayout.setOrientation( QPageLayout::Landscape );
116  pageLayout.setPageSize( QPageSize( QSizeF( size.height(), size.width() ), QPageSize::Millimeter ) );
117  }
118  else
119  {
120  pageLayout.setOrientation( QPageLayout::Portrait );
121  pageLayout.setPageSize( QPageSize( size, QPageSize::Millimeter ) );
122  }
123  pageLayout.setUnits( QPageLayout::Millimeter );
124  return pageLayout;
125 }
126 
128 {
129  return sizeWithUnits();
130 }
131 
133 {
134  if ( sizeWithUnits().width() >= sizeWithUnits().height() )
135  return Landscape;
136  else
137  return Portrait;
138 }
139 
141 {
142  mPageStyleSymbol.reset( symbol );
143  update();
144 }
145 
147 {
148  if ( ok )
149  *ok = false;
150 
151  const QString trimmedString = string.trimmed();
152  if ( trimmedString.compare( QLatin1String( "portrait" ), Qt::CaseInsensitive ) == 0 )
153  {
154  if ( ok )
155  *ok = true;
156  return Portrait;
157  }
158  else if ( trimmedString.compare( QLatin1String( "landscape" ), Qt::CaseInsensitive ) == 0 )
159  {
160  if ( ok )
161  *ok = true;
162  return Landscape;
163  }
164  return Landscape;
165 }
166 
168 {
169  if ( mBoundingRect.isNull() )
170  {
171  const double shadowWidth = mLayout->pageCollection()->pageShadowWidth();
172  mBoundingRect = rect();
173  mBoundingRect.adjust( 0, 0, shadowWidth, shadowWidth );
174  }
175  return mBoundingRect;
176 }
177 
178 void QgsLayoutItemPage::attemptResize( const QgsLayoutSize &size, bool includesFrame )
179 {
180  QgsLayoutItem::attemptResize( size, includesFrame );
181  //update size of attached grid to reflect new page size and position
182  mGrid->setRect( 0, 0, rect().width(), rect().height() );
183 
184  mLayout->guides().update();
185 }
186 
187 void QgsLayoutItemPage::createDefaultPageStyleSymbol()
188 {
189  QVariantMap properties;
190  properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
191  properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
192  properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "no" ) );
193  properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
194  mPageStyleSymbol.reset( QgsFillSymbol::createSimple( properties ) );
195 }
196 
197 
198 
200 class QgsLayoutItemPageUndoCommand: public QgsLayoutItemUndoCommand
201 {
202  public:
203 
204  QgsLayoutItemPageUndoCommand( QgsLayoutItemPage *page, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr )
205  : QgsLayoutItemUndoCommand( page, text, id, parent )
206  {}
207 
208  void restoreState( QDomDocument &stateDoc ) override
209  {
210  QgsLayoutItemUndoCommand::restoreState( stateDoc );
211  layout()->pageCollection()->reflow();
212  }
213 
214  protected:
215 
216  QgsLayoutItem *recreateItem( int, QgsLayout *layout ) override
217  {
218  QgsLayoutItemPage *page = new QgsLayoutItemPage( layout );
219  layout->pageCollection()->addPage( page );
220  return page;
221  }
222 };
224 
225 QgsAbstractLayoutUndoCommand *QgsLayoutItemPage::createCommand( const QString &text, int id, QUndoCommand *parent )
226 {
227  return new QgsLayoutItemPageUndoCommand( this, text, id, parent );
228 }
229 
231 {
233 }
234 
236 {
237  QgsStyleSymbolEntity entity( mPageStyleSymbol.get() );
238  if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "page" ), QObject::tr( "Page" ) ) ) )
239  return false;
240  return true;
241 }
242 
244 {
246  mGrid->update();
247 }
248 
250 {
251  if ( !context.renderContext().painter() || !mLayout || !mLayout->renderContext().pagesVisible() )
252  {
253  return;
254  }
255 
256  const double scale = context.renderContext().convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
257 
258  const QgsExpressionContext expressionContext = createExpressionContext();
259  context.renderContext().setExpressionContext( expressionContext );
260 
261  QPainter *painter = context.renderContext().painter();
262  const QgsScopedQPainterState painterState( painter );
263 
264  if ( mLayout->renderContext().isPreviewRender() )
265  {
266  //if in preview mode, draw page border and shadow so that it's
267  //still possible to tell where pages with a transparent style begin and end
268  painter->setRenderHint( QPainter::Antialiasing, false );
269 
270  const QRectF pageRect = QRectF( 0, 0, scale * rect().width(), scale * rect().height() );
271 
272  //shadow
273  painter->setBrush( QBrush( QColor( 150, 150, 150 ) ) );
274  painter->setPen( Qt::NoPen );
275  painter->drawRect( pageRect.translated( std::min( scale * mLayout->pageCollection()->pageShadowWidth(), mMaximumShadowWidth ),
276  std::min( scale * mLayout->pageCollection()->pageShadowWidth(), mMaximumShadowWidth ) ) );
277 
278  //page area
279  painter->setBrush( QColor( 215, 215, 215 ) );
280  QPen pagePen = QPen( QColor( 100, 100, 100 ), 0 );
281  pagePen.setJoinStyle( Qt::MiterJoin );
282  pagePen.setCosmetic( true );
283  painter->setPen( pagePen );
284  painter->drawRect( pageRect );
285  }
286 
287  if ( mPageStyleSymbol )
288  {
289  std::unique_ptr< QgsFillSymbol > symbol( mPageStyleSymbol->clone() );
290  symbol->startRender( context.renderContext() );
291 
292  //get max bleed from symbol
293  double maxBleedPixels = QgsSymbolLayerUtils::estimateMaxSymbolBleed( symbol.get(), context.renderContext() );
294 
295  //Now subtract 1 pixel to prevent semi-transparent borders at edge of solid page caused by
296  //anti-aliased painting. This may cause a pixel to be cropped from certain edge lines/symbols,
297  //but that can be counteracted by adding a dummy transparent line symbol layer with a wider line width
298  if ( !mLayout->renderContext().isPreviewRender() || !qgsDoubleNear( maxBleedPixels, 0.0 ) )
299  {
300  maxBleedPixels = std::floor( maxBleedPixels - 2 );
301  }
302 
303  // round up
304  const QPolygonF pagePolygon = QPolygonF( QRectF( maxBleedPixels, maxBleedPixels,
305  std::ceil( rect().width() * scale ) - 2 * maxBleedPixels, std::ceil( rect().height() * scale ) - 2 * maxBleedPixels ) );
306  const QVector<QPolygonF> rings; //empty list
307 
308  symbol->renderPolygon( pagePolygon, &rings, nullptr, context.renderContext() );
309  symbol->stopRender( context.renderContext() );
310  }
311 }
312 
314 {}
315 
317 {}
318 
319 bool QgsLayoutItemPage::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
320 {
321  const QDomElement styleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mPageStyleSymbol.get(), document, context );
322  element.appendChild( styleElem );
323  return true;
324 }
325 
326 bool QgsLayoutItemPage::readPropertiesFromElement( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext &context )
327 {
328  const QDomElement symbolElem = element.firstChildElement( QStringLiteral( "symbol" ) );
329  if ( !symbolElem.isNull() )
330  {
331  mPageStyleSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElem, context ) );
332  }
333  else
334  {
335  createDefaultPageStyleSymbol();
336  }
337 
338  return true;
339 }
340 
341 //
342 // QgsLayoutItemPageGrid
343 //
345 
346 QgsLayoutItemPageGrid::QgsLayoutItemPageGrid( double x, double y, double width, double height, QgsLayout *layout )
347  : QGraphicsRectItem( 0, 0, width, height )
348  , mLayout( layout )
349 {
350  // needed to access current view transform during paint operations
351  setFlags( flags() | QGraphicsItem::ItemUsesExtendedStyleOption );
352  setCacheMode( QGraphicsItem::DeviceCoordinateCache );
353  setFlag( QGraphicsItem::ItemIsSelectable, false );
354  setFlag( QGraphicsItem::ItemIsMovable, false );
355  setZValue( QgsLayout::ZGrid );
356  setPos( x, y );
357 }
358 
359 void QgsLayoutItemPageGrid::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
360 {
361  Q_UNUSED( pWidget )
362 
363  //draw grid
364  if ( !mLayout )
365  return;
366 
367  if ( !mLayout->renderContext().isPreviewRender() )
368  return;
369 
370  const QgsLayoutRenderContext &context = mLayout->renderContext();
371  const QgsLayoutGridSettings &grid = mLayout->gridSettings();
372 
373  if ( !context.gridVisible() || grid.resolution().length() <= 0 )
374  return;
375 
376  const QPointF gridOffset = mLayout->convertToLayoutUnits( grid.offset() );
377  const double gridResolution = mLayout->convertToLayoutUnits( grid.resolution() );
378  const int gridMultiplyX = static_cast< int >( gridOffset.x() / gridResolution );
379  const int gridMultiplyY = static_cast< int >( gridOffset.y() / gridResolution );
380  double currentXCoord = gridOffset.x() - gridMultiplyX * gridResolution;
381  double currentYCoord;
382  const double minYCoord = gridOffset.y() - gridMultiplyY * gridResolution;
383 
384  const QgsScopedQPainterState painterState( painter );
385  //turn of antialiasing so grid is nice and sharp
386  painter->setRenderHint( QPainter::Antialiasing, false );
387 
388  switch ( grid.style() )
389  {
391  {
392  painter->setPen( grid.pen() );
393 
394  //draw vertical lines
395  for ( ; currentXCoord <= rect().width(); currentXCoord += gridResolution )
396  {
397  painter->drawLine( QPointF( currentXCoord, 0 ), QPointF( currentXCoord, rect().height() ) );
398  }
399 
400  //draw horizontal lines
401  currentYCoord = minYCoord;
402  for ( ; currentYCoord <= rect().height(); currentYCoord += gridResolution )
403  {
404  painter->drawLine( QPointF( 0, currentYCoord ), QPointF( rect().width(), currentYCoord ) );
405  }
406  break;
407  }
408 
411  {
412  const QPen gridPen = grid.pen();
413  painter->setPen( gridPen );
414  painter->setBrush( QBrush( gridPen.color() ) );
415  double halfCrossLength = 1;
416  if ( grid.style() == QgsLayoutGridSettings::StyleDots )
417  {
418  //dots are actually drawn as tiny crosses a few pixels across
419  //set halfCrossLength to equivalent of 1 pixel
420  halfCrossLength = 1 / QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle, painter );
421  }
422  else
423  {
424  halfCrossLength = gridResolution / 6;
425  }
426 
427  for ( ; currentXCoord <= rect().width(); currentXCoord += gridResolution )
428  {
429  currentYCoord = minYCoord;
430  for ( ; currentYCoord <= rect().height(); currentYCoord += gridResolution )
431  {
432  painter->drawLine( QPointF( currentXCoord - halfCrossLength, currentYCoord ), QPointF( currentXCoord + halfCrossLength, currentYCoord ) );
433  painter->drawLine( QPointF( currentXCoord, currentYCoord - halfCrossLength ), QPointF( currentXCoord, currentYCoord + halfCrossLength ) );
434  }
435  }
436  break;
437  }
438  }
439 }
440 
Base class for commands to undo/redo layout and layout object changes.
static QgsPageSizeRegistry * pageSizeRegistry()
Returns the application's page size registry, used for managing layout page sizes.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
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.
Contains settings relating to the appearance, spacing and offset for layout grids.
QgsLayoutMeasurement resolution() const
Returns the page/snap grid resolution.
QgsLayoutPoint offset() const
Returns the offset of the page/snap grid.
Style style() const
Returns the style used for drawing the page/snap grids.
QPen pen() const
Returns the pen used for drawing page/snap grids.
Item representing the paper in a layout.
~QgsLayoutItemPage() override
bool readPropertiesFromElement(const QDomElement &itemElement, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
QRectF boundingRect() const override
void setPageSize(const QgsLayoutSize &size)
Sets the size of the page.
int type() const override
QgsLayoutSize pageSize() const
Returns the size of the page.
void draw(QgsLayoutItemRenderContext &context) override
Draws the item's contents using the specified item render context.
bool accept(QgsStyleEntityVisitorInterface *visitor) const override
Accepts the specified style entity visitor, causing it to visit all style entities associated with th...
void attemptResize(const QgsLayoutSize &size, bool includesFrame=false) override
Attempts to resize the item to a specified target size.
Orientation orientation() const
Returns the page orientation.
QString displayName() const override
Gets item display name.
void drawBackground(QgsRenderContext &context) override
Draws the background for the item.
QgsAbstractLayoutUndoCommand * createCommand(const QString &text, int id, QUndoCommand *parent=nullptr) override
Creates a new layout undo command with the specified text and parent.
static QgsLayoutItemPage * create(QgsLayout *layout)
Returns a new page item for the specified layout.
QgsLayoutItemPage(QgsLayout *layout)
Constructor for QgsLayoutItemPage, with the specified parent layout.
void setPageStyleSymbol(QgsFillSymbol *symbol)
Sets the symbol to use for drawing the page background.
Orientation
Page orientation.
@ Landscape
Landscape orientation.
@ Portrait
Portrait orientation.
void drawFrame(QgsRenderContext &context) override
Draws the frame around the item.
ExportLayerBehavior exportLayerBehavior() const override
Returns the behavior of this item during exporting to layered exports (e.g.
static QgsLayoutItemPage::Orientation decodePageOrientation(const QString &string, bool *ok=nullptr)
Decodes a string representing a page orientation.
bool writePropertiesToElement(QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
QPageLayout pageLayout() const
Returns the page layout for the page, suitable to pass to QPrinter::setPageLayout.
void redraw() override
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
Base class for graphical items within a QgsLayout.
QgsLayoutSize sizeWithUnits() const
Returns the item's current size, including units.
virtual void redraw()
Triggers a redraw (update) of the item.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
virtual void attemptResize(const QgsLayoutSize &size, bool includesFrame=false)
Attempts to resize the item to a specified target size.
void sizePositionChanged()
Emitted when the item's size or position changes.
ExportLayerBehavior
Behavior of item when exporting to layered outputs.
@ CanGroupWithItemsOfSameType
Item can only be placed on layers with other items of the same type, but multiple items of this type ...
QgsLayoutMeasurement convert(QgsLayoutMeasurement measurement, QgsUnitTypes::LayoutUnit targetUnits) const
Converts a measurement from one unit to another.
double length() const
Returns the length of the measurement.
const QgsLayout * layout() const
Returns the layout the object is attached to.
QPointer< QgsLayout > mLayout
void addPage(QgsLayoutItemPage *page)
Adds a page to the collection.
Stores information relating to the current rendering settings for a layout.
bool gridVisible() const
Returns true if the page grid should be drawn.
const QgsLayoutMeasurementConverter & measurementConverter() const
Returns the layout measurement converter to be used in the layout.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:41
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
void setWidth(const double width)
Sets the width for the size.
Definition: qgslayoutsize.h:83
double width() const
Returns the width of the size.
Definition: qgslayoutsize.h:76
void setHeight(const double height)
Sets the height for the size.
Definition: qgslayoutsize.h:97
static Q_DECL_DEPRECATED double scaleFactorFromItemStyle(const QStyleOptionGraphicsItem *style)
Extracts the scale factor from an item style.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:51
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout's render context, which stores information relating to the current ...
Definition: qgslayout.cpp:359
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
Definition: qgslayout.cpp:459
@ ZPage
Z-value for page (paper) items.
Definition: qgslayout.h:59
@ ZGrid
Z-value for page grids.
Definition: qgslayout.h:61
A named page size for layouts.
QgsLayoutSize size
Page size.
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).
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
Scoped object for saving and restoring a QPainter object's state.
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.
@ LayoutMillimeters
Millimeters.
Definition: qgsunittypes.h:183
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:169
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1504
#define SIP_TRANSFERTHIS
Definition: qgis_sip.h:53
Contains information relating to the style entity currently being visited.