QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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.width( QStringLiteral( "X" ) );
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 
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  QgsStringMap 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 {
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  painter->save();
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  QList<QPolygonF> rings; //empty list
284 
285  symbol->renderPolygon( pagePolygon, &rings, nullptr, context.renderContext() );
286  symbol->stopRender( context.renderContext() );
287  }
288 
289  painter->restore();
290 }
291 
293 {}
294 
296 {}
297 
298 bool QgsLayoutItemPage::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
299 {
300  QDomElement styleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mPageStyleSymbol.get(), document, context );
301  element.appendChild( styleElem );
302  return true;
303 }
304 
305 bool QgsLayoutItemPage::readPropertiesFromElement( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext &context )
306 {
307  QDomElement symbolElem = element.firstChildElement( QStringLiteral( "symbol" ) );
308  if ( !symbolElem.isNull() )
309  {
310  mPageStyleSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElem, context ) );
311  }
312  else
313  {
314  createDefaultPageStyleSymbol();
315  }
316 
317  return true;
318 }
319 
320 //
321 // QgsLayoutItemPageGrid
322 //
324 
325 QgsLayoutItemPageGrid::QgsLayoutItemPageGrid( double x, double y, double width, double height, QgsLayout *layout )
326  : QGraphicsRectItem( 0, 0, width, height )
327  , mLayout( layout )
328 {
329  // needed to access current view transform during paint operations
330  setFlags( flags() | QGraphicsItem::ItemUsesExtendedStyleOption );
331  setCacheMode( QGraphicsItem::DeviceCoordinateCache );
332  setFlag( QGraphicsItem::ItemIsSelectable, false );
333  setFlag( QGraphicsItem::ItemIsMovable, false );
334  setZValue( QgsLayout::ZGrid );
335  setPos( x, y );
336 }
337 
338 void QgsLayoutItemPageGrid::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
339 {
340  Q_UNUSED( pWidget )
341 
342  //draw grid
343  if ( !mLayout )
344  return;
345 
346  if ( !mLayout->renderContext().isPreviewRender() )
347  return;
348 
349  const QgsLayoutRenderContext &context = mLayout->renderContext();
350  const QgsLayoutGridSettings &grid = mLayout->gridSettings();
351 
352  if ( !context.gridVisible() || grid.resolution().length() <= 0 )
353  return;
354 
355  QPointF gridOffset = mLayout->convertToLayoutUnits( grid.offset() );
356  double gridResolution = mLayout->convertToLayoutUnits( grid.resolution() );
357  int gridMultiplyX = static_cast< int >( gridOffset.x() / gridResolution );
358  int gridMultiplyY = static_cast< int >( gridOffset.y() / gridResolution );
359  double currentXCoord = gridOffset.x() - gridMultiplyX * gridResolution;
360  double currentYCoord;
361  double minYCoord = gridOffset.y() - gridMultiplyY * gridResolution;
362 
363  painter->save();
364  //turn of antialiasing so grid is nice and sharp
365  painter->setRenderHint( QPainter::Antialiasing, false );
366 
367  switch ( grid.style() )
368  {
370  {
371  painter->setPen( grid.pen() );
372 
373  //draw vertical lines
374  for ( ; currentXCoord <= rect().width(); currentXCoord += gridResolution )
375  {
376  painter->drawLine( QPointF( currentXCoord, 0 ), QPointF( currentXCoord, rect().height() ) );
377  }
378 
379  //draw horizontal lines
380  currentYCoord = minYCoord;
381  for ( ; currentYCoord <= rect().height(); currentYCoord += gridResolution )
382  {
383  painter->drawLine( QPointF( 0, currentYCoord ), QPointF( rect().width(), currentYCoord ) );
384  }
385  break;
386  }
387 
390  {
391  QPen gridPen = grid.pen();
392  painter->setPen( gridPen );
393  painter->setBrush( QBrush( gridPen.color() ) );
394  double halfCrossLength = 1;
395  if ( grid.style() == QgsLayoutGridSettings::StyleDots )
396  {
397  //dots are actually drawn as tiny crosses a few pixels across
398  //set halfCrossLength to equivalent of 1 pixel
399  halfCrossLength = 1 / QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle );
400  }
401  else
402  {
403  halfCrossLength = gridResolution / 6;
404  }
405 
406  for ( ; currentXCoord <= rect().width(); currentXCoord += gridResolution )
407  {
408  currentYCoord = minYCoord;
409  for ( ; currentYCoord <= rect().height(); currentYCoord += gridResolution )
410  {
411  painter->drawLine( QPointF( currentXCoord - halfCrossLength, currentYCoord ), QPointF( currentXCoord + halfCrossLength, currentYCoord ) );
412  painter->drawLine( QPointF( currentXCoord, currentYCoord - halfCrossLength ), QPointF( currentXCoord, currentYCoord + halfCrossLength ) );
413  }
414  }
415  break;
416  }
417  }
418  painter->restore();
419 }
420 
The class is used as a container of context for various read/write operations on other objects...
int type() const override
static double scaleFactorFromItemStyle(const QStyleOptionGraphicsItem *style)
Extracts the scale factor from an item style.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Portrait orientation.
A symbol entity for QgsStyle databases.
Definition: qgsstyle.h:971
Base class for graphical items within a QgsLayout.
#define SIP_TRANSFERTHIS
Definition: qgis_sip.h:53
void drawFrame(QgsRenderContext &context) override
Draws the frame around the item.
Base class for commands to undo/redo layout and layout object changes.
Landscape orientation.
Z-value for page grids.
Definition: qgslayout.h:60
QString displayName() const override
Gets item display name.
QRectF boundingRect() const override
static QgsFillSymbol * createSimple(const QgsStringMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties. ...
Definition: qgssymbol.cpp:1251
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:280
QgsLayoutSize sizeWithUnits() const
Returns the item&#39;s current size, including units.
~QgsLayoutItemPage() override
void setPageStyleSymbol(QgsFillSymbol *symbol)
Sets the symbol to use for drawing the page background.
QPen pen() const
Returns the pen used for drawing page/snap grids.
void redraw() override
Z-value for page (paper) items.
Definition: qgslayout.h:58
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
Item can only be placed on layers with other items of the same type, but multiple items of this type ...
An interface for classes which can visit style entity (e.g.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:612
virtual bool visit(const QgsStyleEntityVisitorInterface::StyleLeaf &entity)
Called when the visitor will visit a style entity.
bool gridVisible() const
Returns true if the page grid should be drawn.
void sizePositionChanged()
Emitted when the item&#39;s size or position changes.
static QgsLayoutItemPage::Orientation decodePageOrientation(const QString &string, bool *ok=nullptr)
Decodes a string representing a page orientation.
const QgsFillSymbol * pageStyleSymbol() const
Returns the symbol to use for drawing the page background.
const QgsLayout * layout() const
Returns the layout the object is attached to.
QgsLayoutMeasurement resolution() const
Returns the page/snap grid resolution.
void attemptResize(const QgsLayoutSize &size, bool includesFrame=false) override
Attempts to resize the item to a specified target size.
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout&#39;s page collection, which stores and manages page items in the layout...
Definition: qgslayout.cpp:458
QgsAbstractLayoutUndoCommand * createCommand(const QString &text, int id, QUndoCommand *parent=nullptr) override
Creates a new layout undo command with the specified text and parent.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsRenderContext & renderContext()
Returns a reference to the context&#39;s render context.
Definition: qgslayoutitem.h:72
QPointer< QgsLayout > mLayout
bool writePropertiesToElement(QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores item state within an XML DOM element.
virtual void attemptResize(const QgsLayoutSize &size, bool includesFrame=false)
Attempts to resize the item to a specified target size.
bool readPropertiesFromElement(const QDomElement &itemElement, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets item state from a DOM element.
Orientation orientation() const
Returns the page orientation.
int page() const
Returns the page the item is currently on, with the first page returning 0.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
void setHeight(const double height)
Sets the height for the size.
Definition: qgslayoutsize.h:97
Contains settings relating to the appearance, spacing and offset for layout grids.
virtual void redraw()
Triggers a redraw (update) of the item.
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:44
void draw(QgsLayoutItemRenderContext &context) override
Draws the item&#39;s contents using the specified item render context.
QgsLayoutPoint offset() const
Returns the offset of the page/snap grid.
QgsLayoutItemPage(QgsLayout *layout)
Constructor for QgsLayoutItemPage, with the specified parent layout.
QgsLayoutSize pageSize() const
Returns the size of the page.
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
ExportLayerBehavior
Behavior of item when exporting to layered outputs.
double length() const
Returns the length of the measurement.
QPainter * painter()
Returns the destination QPainter for the render operation.
static QgsPageSizeRegistry * pageSizeRegistry()
Returns the application&#39;s page size registry, used for managing layout page sizes.
A named page size for layouts.
bool accept(QgsStyleEntityVisitorInterface *visitor) const override
Accepts the specified style entity visitor, causing it to visit all style entities associated with th...
Style style() const
Returns the style used for drawing the page/snap grids.
Orientation
Page orientation.
Stores information relating to the current rendering settings for a layout.
static QgsLayoutItemPage * create(QgsLayout *layout)
Returns a new page item for the specified layout.
void drawBackground(QgsRenderContext &context) override
Draws the background for the item.
void addPage(QgsLayoutItemPage *page)
Adds a page to the collection.
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
Definition: qgssymbol.h:1155
void setPageSize(const QgsLayoutSize &size)
Sets the size of the page.
ExportLayerBehavior exportLayerBehavior() const override
Returns the behavior of this item during exporting to layered exports (e.g.
void reflow()
Forces the page collection to reflow the arrangement of pages, e.g.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:40
QgsLayoutSize size
Page size.
double height() const
Returns the height of the size.
Definition: qgslayoutsize.h:90
Contains information relating to the style entity currently being visited.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
void setWidth(const double width)
Sets the width for the size.
Definition: qgslayoutsize.h:83
Item representing the paper in a layout.
double width() const
Returns the width of the size.
Definition: qgslayoutsize.h:76