QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 <QPainter>
28 #include <QStyleOptionGraphicsItem>
29 
31  : QgsLayoutItem( layout, false )
32 {
33  setFlag( QGraphicsItem::ItemIsSelectable, false );
34  setFlag( QGraphicsItem::ItemIsMovable, false );
35  setZValue( QgsLayout::ZPage );
36 
37  connect( this, &QgsLayoutItem::sizePositionChanged, this, [ = ]
38  {
39  mBoundingRect = QRectF();
40  prepareGeometryChange();
41  } );
42 
43  QFont font;
44  QFontMetrics fm( font );
45  mMaximumShadowWidth = fm.boundingRect( QStringLiteral( "X" ) ).width();
46 
47  mGrid.reset( new QgsLayoutItemPageGrid( pos().x(), pos().y(), rect().width(), rect().height(), mLayout ) );
48  mGrid->setParentItem( this );
49 
50  createDefaultPageStyleSymbol();
51 }
52 
54 
56 {
57  return new QgsLayoutItemPage( layout );
58 }
59 
61 {
63 }
64 
66 {
67  return QObject::tr( "Page" );
68 }
69 
71 {
72  attemptResize( size );
73 }
74 
75 bool QgsLayoutItemPage::setPageSize( const QString &size, Orientation orientation )
76 {
77  QgsPageSize newSize;
78  if ( QgsApplication::pageSizeRegistry()->decodePageSize( size, newSize ) )
79  {
80  switch ( orientation )
81  {
82  case Portrait:
83  break; // nothing to do
84 
85  case Landscape:
86  {
87  // flip height and width
88  double x = newSize.size.width();
89  newSize.size.setWidth( newSize.size.height() );
90  newSize.size.setHeight( x );
91  break;
92  }
93  }
94 
95  setPageSize( newSize.size );
96  return true;
97  }
98  else
99  {
100  return false;
101  }
102 }
103 
105 {
106  return sizeWithUnits();
107 }
108 
110 {
111  if ( sizeWithUnits().width() >= sizeWithUnits().height() )
112  return Landscape;
113  else
114  return Portrait;
115 }
116 
118 {
119  mPageStyleSymbol.reset( symbol );
120  update();
121 }
122 
124 {
125  if ( ok )
126  *ok = false;
127 
128  QString trimmedString = string.trimmed();
129  if ( trimmedString.compare( QLatin1String( "portrait" ), Qt::CaseInsensitive ) == 0 )
130  {
131  if ( ok )
132  *ok = true;
133  return Portrait;
134  }
135  else if ( trimmedString.compare( QLatin1String( "landscape" ), Qt::CaseInsensitive ) == 0 )
136  {
137  if ( ok )
138  *ok = true;
139  return Landscape;
140  }
141  return Landscape;
142 }
143 
145 {
146  if ( mBoundingRect.isNull() )
147  {
148  double shadowWidth = mLayout->pageCollection()->pageShadowWidth();
149  mBoundingRect = rect();
150  mBoundingRect.adjust( 0, 0, shadowWidth, shadowWidth );
151  }
152  return mBoundingRect;
153 }
154 
155 void QgsLayoutItemPage::attemptResize( const QgsLayoutSize &size, bool includesFrame )
156 {
157  QgsLayoutItem::attemptResize( size, includesFrame );
158  //update size of attached grid to reflect new page size and position
159  mGrid->setRect( 0, 0, rect().width(), rect().height() );
160 
161  mLayout->guides().update();
162 }
163 
164 void QgsLayoutItemPage::createDefaultPageStyleSymbol()
165 {
166  QVariantMap properties;
167  properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
168  properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
169  properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "no" ) );
170  properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
171  mPageStyleSymbol.reset( QgsFillSymbol::createSimple( properties ) );
172 }
173 
174 
175 
177 class QgsLayoutItemPageUndoCommand: public QgsLayoutItemUndoCommand
178 {
179  public:
180 
181  QgsLayoutItemPageUndoCommand( QgsLayoutItemPage *page, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr )
182  : QgsLayoutItemUndoCommand( page, text, id, parent )
183  {}
184 
185  void restoreState( QDomDocument &stateDoc ) override
186  {
187  QgsLayoutItemUndoCommand::restoreState( stateDoc );
188  layout()->pageCollection()->reflow();
189  }
190 
191  protected:
192 
193  QgsLayoutItem *recreateItem( int, QgsLayout *layout ) override
194  {
195  QgsLayoutItemPage *page = new QgsLayoutItemPage( layout );
196  layout->pageCollection()->addPage( page );
197  return page;
198  }
199 };
201 
202 QgsAbstractLayoutUndoCommand *QgsLayoutItemPage::createCommand( const QString &text, int id, QUndoCommand *parent )
203 {
204  return new QgsLayoutItemPageUndoCommand( this, text, id, parent );
205 }
206 
208 {
210 }
211 
213 {
214  QgsStyleSymbolEntity entity( mPageStyleSymbol.get() );
215  if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "page" ), QObject::tr( "Page" ) ) ) )
216  return false;
217  return true;
218 }
219 
221 {
223  mGrid->update();
224 }
225 
227 {
228  if ( !context.renderContext().painter() || !mLayout || !mLayout->renderContext().pagesVisible() )
229  {
230  return;
231  }
232 
234 
235  QgsExpressionContext expressionContext = createExpressionContext();
236  context.renderContext().setExpressionContext( expressionContext );
237 
238  QPainter *painter = context.renderContext().painter();
239  QgsScopedQPainterState painterState( painter );
240 
241  if ( mLayout->renderContext().isPreviewRender() )
242  {
243  //if in preview mode, draw page border and shadow so that it's
244  //still possible to tell where pages with a transparent style begin and end
245  painter->setRenderHint( QPainter::Antialiasing, false );
246 
247  QRectF pageRect = QRectF( 0, 0, scale * rect().width(), scale * rect().height() );
248 
249  //shadow
250  painter->setBrush( QBrush( QColor( 150, 150, 150 ) ) );
251  painter->setPen( Qt::NoPen );
252  painter->drawRect( pageRect.translated( std::min( scale * mLayout->pageCollection()->pageShadowWidth(), mMaximumShadowWidth ),
253  std::min( scale * mLayout->pageCollection()->pageShadowWidth(), mMaximumShadowWidth ) ) );
254 
255  //page area
256  painter->setBrush( QColor( 215, 215, 215 ) );
257  QPen pagePen = QPen( QColor( 100, 100, 100 ), 0 );
258  pagePen.setJoinStyle( Qt::MiterJoin );
259  pagePen.setCosmetic( true );
260  painter->setPen( pagePen );
261  painter->drawRect( pageRect );
262  }
263 
264  if ( mPageStyleSymbol )
265  {
266  std::unique_ptr< QgsFillSymbol > symbol( mPageStyleSymbol->clone() );
267  symbol->startRender( context.renderContext() );
268 
269  //get max bleed from symbol
270  double maxBleedPixels = QgsSymbolLayerUtils::estimateMaxSymbolBleed( symbol.get(), context.renderContext() );
271 
272  //Now subtract 1 pixel to prevent semi-transparent borders at edge of solid page caused by
273  //anti-aliased painting. This may cause a pixel to be cropped from certain edge lines/symbols,
274  //but that can be counteracted by adding a dummy transparent line symbol layer with a wider line width
275  if ( !mLayout->renderContext().isPreviewRender() || !qgsDoubleNear( maxBleedPixels, 0.0 ) )
276  {
277  maxBleedPixels = std::floor( maxBleedPixels - 2 );
278  }
279 
280  // round up
281  QPolygonF pagePolygon = QPolygonF( QRectF( maxBleedPixels, maxBleedPixels,
282  std::ceil( rect().width() * scale ) - 2 * maxBleedPixels, std::ceil( rect().height() * scale ) - 2 * maxBleedPixels ) );
283  QVector<QPolygonF> rings; //empty list
284 
285  symbol->renderPolygon( pagePolygon, &rings, nullptr, context.renderContext() );
286  symbol->stopRender( context.renderContext() );
287  }
288 }
289 
291 {}
292 
294 {}
295 
296 bool QgsLayoutItemPage::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
297 {
298  QDomElement styleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mPageStyleSymbol.get(), document, context );
299  element.appendChild( styleElem );
300  return true;
301 }
302 
303 bool QgsLayoutItemPage::readPropertiesFromElement( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext &context )
304 {
305  QDomElement symbolElem = element.firstChildElement( QStringLiteral( "symbol" ) );
306  if ( !symbolElem.isNull() )
307  {
308  mPageStyleSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElem, context ) );
309  }
310  else
311  {
312  createDefaultPageStyleSymbol();
313  }
314 
315  return true;
316 }
317 
318 //
319 // QgsLayoutItemPageGrid
320 //
322 
323 QgsLayoutItemPageGrid::QgsLayoutItemPageGrid( double x, double y, double width, double height, QgsLayout *layout )
324  : QGraphicsRectItem( 0, 0, width, height )
325  , mLayout( layout )
326 {
327  // needed to access current view transform during paint operations
328  setFlags( flags() | QGraphicsItem::ItemUsesExtendedStyleOption );
329  setCacheMode( QGraphicsItem::DeviceCoordinateCache );
330  setFlag( QGraphicsItem::ItemIsSelectable, false );
331  setFlag( QGraphicsItem::ItemIsMovable, false );
332  setZValue( QgsLayout::ZGrid );
333  setPos( x, y );
334 }
335 
336 void QgsLayoutItemPageGrid::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
337 {
338  Q_UNUSED( pWidget )
339 
340  //draw grid
341  if ( !mLayout )
342  return;
343 
344  if ( !mLayout->renderContext().isPreviewRender() )
345  return;
346 
347  const QgsLayoutRenderContext &context = mLayout->renderContext();
348  const QgsLayoutGridSettings &grid = mLayout->gridSettings();
349 
350  if ( !context.gridVisible() || grid.resolution().length() <= 0 )
351  return;
352 
353  QPointF gridOffset = mLayout->convertToLayoutUnits( grid.offset() );
354  double gridResolution = mLayout->convertToLayoutUnits( grid.resolution() );
355  int gridMultiplyX = static_cast< int >( gridOffset.x() / gridResolution );
356  int gridMultiplyY = static_cast< int >( gridOffset.y() / gridResolution );
357  double currentXCoord = gridOffset.x() - gridMultiplyX * gridResolution;
358  double currentYCoord;
359  double minYCoord = gridOffset.y() - gridMultiplyY * gridResolution;
360 
361  QgsScopedQPainterState painterState( painter );
362  //turn of antialiasing so grid is nice and sharp
363  painter->setRenderHint( QPainter::Antialiasing, false );
364 
365  switch ( grid.style() )
366  {
368  {
369  painter->setPen( grid.pen() );
370 
371  //draw vertical lines
372  for ( ; currentXCoord <= rect().width(); currentXCoord += gridResolution )
373  {
374  painter->drawLine( QPointF( currentXCoord, 0 ), QPointF( currentXCoord, rect().height() ) );
375  }
376 
377  //draw horizontal lines
378  currentYCoord = minYCoord;
379  for ( ; currentYCoord <= rect().height(); currentYCoord += gridResolution )
380  {
381  painter->drawLine( QPointF( 0, currentYCoord ), QPointF( rect().width(), currentYCoord ) );
382  }
383  break;
384  }
385 
388  {
389  QPen gridPen = grid.pen();
390  painter->setPen( gridPen );
391  painter->setBrush( QBrush( gridPen.color() ) );
392  double halfCrossLength = 1;
393  if ( grid.style() == QgsLayoutGridSettings::StyleDots )
394  {
395  //dots are actually drawn as tiny crosses a few pixels across
396  //set halfCrossLength to equivalent of 1 pixel
397  halfCrossLength = 1 / QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle );
398  }
399  else
400  {
401  halfCrossLength = gridResolution / 6;
402  }
403 
404  for ( ; currentXCoord <= rect().width(); currentXCoord += gridResolution )
405  {
406  currentYCoord = minYCoord;
407  for ( ; currentYCoord <= rect().height(); currentYCoord += gridResolution )
408  {
409  painter->drawLine( QPointF( currentXCoord - halfCrossLength, currentYCoord ), QPointF( currentXCoord + halfCrossLength, currentYCoord ) );
410  painter->drawLine( QPointF( currentXCoord, currentYCoord - halfCrossLength ), QPointF( currentXCoord, currentYCoord + halfCrossLength ) );
411  }
412  }
413  break;
414  }
415  }
416 }
417 
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: qgssymbol.h:1307
static QgsFillSymbol * createSimple(const QVariantMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties.
Definition: qgssymbol.cpp:1578
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.
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 ...
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.
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 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:50
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:58
@ ZGrid
Z-value for page grids.
Definition: qgslayout.h:60
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.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
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:1201
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.
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:168
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316
#define SIP_TRANSFERTHIS
Definition: qgis_sip.h:53
Contains information relating to the style entity currently being visited.