QGIS API Documentation  3.9.0-Master (224899f119)
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 <QPainter>
26 #include <QStyleOptionGraphicsItem>
27 
29  : QgsLayoutItem( layout, false )
30 {
31  setFlag( QGraphicsItem::ItemIsSelectable, false );
32  setFlag( QGraphicsItem::ItemIsMovable, false );
33  setZValue( QgsLayout::ZPage );
34 
35  connect( this, &QgsLayoutItem::sizePositionChanged, this, [ = ]
36  {
37  mBoundingRect = QRectF();
38  prepareGeometryChange();
39  } );
40 
41  QFont font;
42  QFontMetrics fm( font );
43  mMaximumShadowWidth = fm.width( QStringLiteral( "X" ) );
44 
45  mGrid.reset( new QgsLayoutItemPageGrid( pos().x(), pos().y(), rect().width(), rect().height(), mLayout ) );
46  mGrid->setParentItem( this );
47 }
48 
50 {
51  return new QgsLayoutItemPage( layout );
52 }
53 
55 {
57 }
58 
60 {
61  return QObject::tr( "Page" );
62 }
63 
65 {
66  attemptResize( size );
67 }
68 
70 {
71  QgsPageSize newSize;
72  if ( QgsApplication::pageSizeRegistry()->decodePageSize( size, newSize ) )
73  {
74  switch ( orientation )
75  {
76  case Portrait:
77  break; // nothing to do
78 
79  case Landscape:
80  {
81  // flip height and width
82  double x = newSize.size.width();
83  newSize.size.setWidth( newSize.size.height() );
84  newSize.size.setHeight( x );
85  break;
86  }
87  }
88 
89  setPageSize( newSize.size );
90  return true;
91  }
92  else
93  {
94  return false;
95  }
96 }
97 
99 {
100  return sizeWithUnits();
101 }
102 
104 {
105  if ( sizeWithUnits().width() >= sizeWithUnits().height() )
106  return Landscape;
107  else
108  return Portrait;
109 }
110 
112 {
113  if ( ok )
114  *ok = false;
115 
116  QString trimmedString = string.trimmed();
117  if ( trimmedString.compare( QLatin1String( "portrait" ), Qt::CaseInsensitive ) == 0 )
118  {
119  if ( ok )
120  *ok = true;
121  return Portrait;
122  }
123  else if ( trimmedString.compare( QLatin1String( "landscape" ), Qt::CaseInsensitive ) == 0 )
124  {
125  if ( ok )
126  *ok = true;
127  return Landscape;
128  }
129  return Landscape;
130 }
131 
133 {
134  if ( mBoundingRect.isNull() )
135  {
136  double shadowWidth = mLayout->pageCollection()->pageShadowWidth();
137  mBoundingRect = rect();
138  mBoundingRect.adjust( 0, 0, shadowWidth, shadowWidth );
139  }
140  return mBoundingRect;
141 }
142 
143 void QgsLayoutItemPage::attemptResize( const QgsLayoutSize &size, bool includesFrame )
144 {
145  QgsLayoutItem::attemptResize( size, includesFrame );
146  //update size of attached grid to reflect new page size and position
147  mGrid->setRect( 0, 0, rect().width(), rect().height() );
148 
149  mLayout->guides().update();
150 }
151 
153 class QgsLayoutItemPageUndoCommand: public QgsLayoutItemUndoCommand
154 {
155  public:
156 
157  QgsLayoutItemPageUndoCommand( QgsLayoutItemPage *page, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr )
158  : QgsLayoutItemUndoCommand( page, text, id, parent )
159  {}
160 
161  void restoreState( QDomDocument &stateDoc ) override
162  {
163  QgsLayoutItemUndoCommand::restoreState( stateDoc );
164  layout()->pageCollection()->reflow();
165  }
166 
167  protected:
168 
169  QgsLayoutItem *recreateItem( int, QgsLayout *layout ) override
170  {
171  QgsLayoutItemPage *page = new QgsLayoutItemPage( layout );
172  layout->pageCollection()->addPage( page );
173  return page;
174  }
175 };
177 
178 QgsAbstractLayoutUndoCommand *QgsLayoutItemPage::createCommand( const QString &text, int id, QUndoCommand *parent )
179 {
180  return new QgsLayoutItemPageUndoCommand( this, text, id, parent );
181 }
182 
184 {
186 }
187 
189 {
191  mGrid->update();
192 }
193 
195 {
196  if ( !context.renderContext().painter() || !mLayout || !mLayout->renderContext().pagesVisible() )
197  {
198  return;
199  }
200 
202 
203  QgsExpressionContext expressionContext = createExpressionContext();
204  context.renderContext().setExpressionContext( expressionContext );
205 
206  QPainter *painter = context.renderContext().painter();
207  painter->save();
208 
209  if ( mLayout->renderContext().isPreviewRender() )
210  {
211  //if in preview mode, draw page border and shadow so that it's
212  //still possible to tell where pages with a transparent style begin and end
213  painter->setRenderHint( QPainter::Antialiasing, false );
214 
215  QRectF pageRect = QRectF( 0, 0, scale * rect().width(), scale * rect().height() );
216 
217  //shadow
218  painter->setBrush( QBrush( QColor( 150, 150, 150 ) ) );
219  painter->setPen( Qt::NoPen );
220  painter->drawRect( pageRect.translated( std::min( scale * mLayout->pageCollection()->pageShadowWidth(), mMaximumShadowWidth ),
221  std::min( scale * mLayout->pageCollection()->pageShadowWidth(), mMaximumShadowWidth ) ) );
222 
223  //page area
224  painter->setBrush( QColor( 215, 215, 215 ) );
225  QPen pagePen = QPen( QColor( 100, 100, 100 ), 0 );
226  pagePen.setJoinStyle( Qt::MiterJoin );
227  pagePen.setCosmetic( true );
228  painter->setPen( pagePen );
229  painter->drawRect( pageRect );
230  }
231 
232  std::unique_ptr< QgsFillSymbol > symbol( mLayout->pageCollection()->pageStyleSymbol()->clone() );
233  symbol->startRender( context.renderContext() );
234 
235  //get max bleed from symbol
236  double maxBleedPixels = QgsSymbolLayerUtils::estimateMaxSymbolBleed( symbol.get(), context.renderContext() );
237 
238  //Now subtract 1 pixel to prevent semi-transparent borders at edge of solid page caused by
239  //anti-aliased painting. This may cause a pixel to be cropped from certain edge lines/symbols,
240  //but that can be counteracted by adding a dummy transparent line symbol layer with a wider line width
241  if ( !mLayout->renderContext().isPreviewRender() || !qgsDoubleNear( maxBleedPixels, 0.0 ) )
242  {
243  maxBleedPixels = std::floor( maxBleedPixels - 2 );
244  }
245 
246  // round up
247  QPolygonF pagePolygon = QPolygonF( QRectF( maxBleedPixels, maxBleedPixels,
248  std::ceil( rect().width() * scale ) - 2 * maxBleedPixels, std::ceil( rect().height() * scale ) - 2 * maxBleedPixels ) );
249  QList<QPolygonF> rings; //empty list
250 
251  symbol->renderPolygon( pagePolygon, &rings, nullptr, context.renderContext() );
252  symbol->stopRender( context.renderContext() );
253 
254  painter->restore();
255 }
256 
258 {}
259 
261 {}
262 
263 //
264 // QgsLayoutItemPageGrid
265 //
267 
268 QgsLayoutItemPageGrid::QgsLayoutItemPageGrid( double x, double y, double width, double height, QgsLayout *layout )
269  : QGraphicsRectItem( 0, 0, width, height )
270  , mLayout( layout )
271 {
272  // needed to access current view transform during paint operations
273  setFlags( flags() | QGraphicsItem::ItemUsesExtendedStyleOption );
274  setCacheMode( QGraphicsItem::DeviceCoordinateCache );
275  setFlag( QGraphicsItem::ItemIsSelectable, false );
276  setFlag( QGraphicsItem::ItemIsMovable, false );
277  setZValue( QgsLayout::ZGrid );
278  setPos( x, y );
279 }
280 
281 void QgsLayoutItemPageGrid::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
282 {
283  Q_UNUSED( pWidget )
284 
285  //draw grid
286  if ( !mLayout )
287  return;
288 
289  if ( !mLayout->renderContext().isPreviewRender() )
290  return;
291 
292  const QgsLayoutRenderContext &context = mLayout->renderContext();
293  const QgsLayoutGridSettings &grid = mLayout->gridSettings();
294 
295  if ( !context.gridVisible() || grid.resolution().length() <= 0 )
296  return;
297 
298  QPointF gridOffset = mLayout->convertToLayoutUnits( grid.offset() );
299  double gridResolution = mLayout->convertToLayoutUnits( grid.resolution() );
300  int gridMultiplyX = static_cast< int >( gridOffset.x() / gridResolution );
301  int gridMultiplyY = static_cast< int >( gridOffset.y() / gridResolution );
302  double currentXCoord = gridOffset.x() - gridMultiplyX * gridResolution;
303  double currentYCoord;
304  double minYCoord = gridOffset.y() - gridMultiplyY * gridResolution;
305 
306  painter->save();
307  //turn of antialiasing so grid is nice and sharp
308  painter->setRenderHint( QPainter::Antialiasing, false );
309 
310  switch ( grid.style() )
311  {
313  {
314  painter->setPen( grid.pen() );
315 
316  //draw vertical lines
317  for ( ; currentXCoord <= rect().width(); currentXCoord += gridResolution )
318  {
319  painter->drawLine( QPointF( currentXCoord, 0 ), QPointF( currentXCoord, rect().height() ) );
320  }
321 
322  //draw horizontal lines
323  currentYCoord = minYCoord;
324  for ( ; currentYCoord <= rect().height(); currentYCoord += gridResolution )
325  {
326  painter->drawLine( QPointF( 0, currentYCoord ), QPointF( rect().width(), currentYCoord ) );
327  }
328  break;
329  }
330 
333  {
334  QPen gridPen = grid.pen();
335  painter->setPen( gridPen );
336  painter->setBrush( QBrush( gridPen.color() ) );
337  double halfCrossLength = 1;
338  if ( grid.style() == QgsLayoutGridSettings::StyleDots )
339  {
340  //dots are actually drawn as tiny crosses a few pixels across
341  //set halfCrossLength to equivalent of 1 pixel
342  halfCrossLength = 1 / QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle );
343  }
344  else
345  {
346  halfCrossLength = gridResolution / 6;
347  }
348 
349  for ( ; currentXCoord <= rect().width(); currentXCoord += gridResolution )
350  {
351  currentYCoord = minYCoord;
352  for ( ; currentYCoord <= rect().height(); currentYCoord += gridResolution )
353  {
354  painter->drawLine( QPointF( currentXCoord - halfCrossLength, currentYCoord ), QPointF( currentXCoord + halfCrossLength, currentYCoord ) );
355  painter->drawLine( QPointF( currentXCoord, currentYCoord - halfCrossLength ), QPointF( currentXCoord, currentYCoord + halfCrossLength ) );
356  }
357  }
358  break;
359  }
360  }
361  painter->restore();
362 }
363 
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.
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
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:265
QgsLayoutSize sizeWithUnits() const
Returns the item&#39;s current size, including units.
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
Item can only be placed on layers with other items of the same type, but multiple items of this type ...
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 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
virtual void attemptResize(const QgsLayoutSize &size, bool includesFrame=false)
Attempts to resize the item to a specified target size.
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.
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.
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
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