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