Branch data Line data Source code
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"
22 : : #include "qgslayoutitemundocommand.h"
23 : : #include "qgslayoutpagecollection.h"
24 : : #include "qgslayoutundostack.h"
25 : : #include "qgsstyle.h"
26 : : #include "qgsstyleentityvisitor.h"
27 : : #include <QPainter>
28 : : #include <QStyleOptionGraphicsItem>
29 : :
30 : 0 : QgsLayoutItemPage::QgsLayoutItemPage( QgsLayout *layout )
31 : 0 : : QgsLayoutItem( layout, false )
32 : 0 : {
33 : 0 : setFlag( QGraphicsItem::ItemIsSelectable, false );
34 : 0 : setFlag( QGraphicsItem::ItemIsMovable, false );
35 : 0 : setZValue( QgsLayout::ZPage );
36 : :
37 : 0 : connect( this, &QgsLayoutItem::sizePositionChanged, this, [ = ]
38 : : {
39 : 0 : mBoundingRect = QRectF();
40 : 0 : prepareGeometryChange();
41 : 0 : } );
42 : :
43 : 0 : QFont font;
44 : 0 : QFontMetrics fm( font );
45 : 0 : mMaximumShadowWidth = fm.boundingRect( QStringLiteral( "X" ) ).width();
46 : :
47 : 0 : mGrid.reset( new QgsLayoutItemPageGrid( pos().x(), pos().y(), rect().width(), rect().height(), mLayout ) );
48 : 0 : mGrid->setParentItem( this );
49 : :
50 : 0 : createDefaultPageStyleSymbol();
51 : 0 : }
52 : :
53 : 0 : QgsLayoutItemPage::~QgsLayoutItemPage() = default;
54 : :
55 : 0 : QgsLayoutItemPage *QgsLayoutItemPage::create( QgsLayout *layout )
56 : : {
57 : 0 : return new QgsLayoutItemPage( layout );
58 : 0 : }
59 : :
60 : 0 : int QgsLayoutItemPage::type() const
61 : : {
62 : 0 : return QgsLayoutItemRegistry::LayoutPage;
63 : : }
64 : :
65 : 0 : QString QgsLayoutItemPage::displayName() const
66 : : {
67 : 0 : return QObject::tr( "Page" );
68 : : }
69 : :
70 : 0 : void QgsLayoutItemPage::setPageSize( const QgsLayoutSize &size )
71 : : {
72 : 0 : attemptResize( size );
73 : 0 : }
74 : :
75 : 0 : bool QgsLayoutItemPage::setPageSize( const QString &size, Orientation orientation )
76 : : {
77 : 0 : QgsPageSize newSize;
78 : 0 : if ( QgsApplication::pageSizeRegistry()->decodePageSize( size, newSize ) )
79 : : {
80 : 0 : switch ( orientation )
81 : : {
82 : : case Portrait:
83 : 0 : break; // nothing to do
84 : :
85 : : case Landscape:
86 : : {
87 : : // flip height and width
88 : 0 : double x = newSize.size.width();
89 : 0 : newSize.size.setWidth( newSize.size.height() );
90 : 0 : newSize.size.setHeight( x );
91 : 0 : break;
92 : : }
93 : : }
94 : :
95 : 0 : setPageSize( newSize.size );
96 : 0 : return true;
97 : : }
98 : : else
99 : : {
100 : 0 : return false;
101 : : }
102 : 0 : }
103 : :
104 : 0 : QgsLayoutSize QgsLayoutItemPage::pageSize() const
105 : : {
106 : 0 : return sizeWithUnits();
107 : : }
108 : :
109 : 0 : QgsLayoutItemPage::Orientation QgsLayoutItemPage::orientation() const
110 : : {
111 : 0 : if ( sizeWithUnits().width() >= sizeWithUnits().height() )
112 : 0 : return Landscape;
113 : : else
114 : 0 : return Portrait;
115 : 0 : }
116 : :
117 : 0 : void QgsLayoutItemPage::setPageStyleSymbol( QgsFillSymbol *symbol )
118 : : {
119 : 0 : mPageStyleSymbol.reset( symbol );
120 : 0 : update();
121 : 0 : }
122 : :
123 : 0 : QgsLayoutItemPage::Orientation QgsLayoutItemPage::decodePageOrientation( const QString &string, bool *ok )
124 : : {
125 : 0 : if ( ok )
126 : 0 : *ok = false;
127 : :
128 : 0 : QString trimmedString = string.trimmed();
129 : 0 : if ( trimmedString.compare( QLatin1String( "portrait" ), Qt::CaseInsensitive ) == 0 )
130 : : {
131 : 0 : if ( ok )
132 : 0 : *ok = true;
133 : 0 : return Portrait;
134 : : }
135 : 0 : else if ( trimmedString.compare( QLatin1String( "landscape" ), Qt::CaseInsensitive ) == 0 )
136 : : {
137 : 0 : if ( ok )
138 : 0 : *ok = true;
139 : 0 : return Landscape;
140 : : }
141 : 0 : return Landscape;
142 : 0 : }
143 : :
144 : 0 : QRectF QgsLayoutItemPage::boundingRect() const
145 : : {
146 : 0 : if ( mBoundingRect.isNull() )
147 : : {
148 : 0 : double shadowWidth = mLayout->pageCollection()->pageShadowWidth();
149 : 0 : mBoundingRect = rect();
150 : 0 : mBoundingRect.adjust( 0, 0, shadowWidth, shadowWidth );
151 : 0 : }
152 : 0 : return mBoundingRect;
153 : : }
154 : :
155 : 0 : void QgsLayoutItemPage::attemptResize( const QgsLayoutSize &size, bool includesFrame )
156 : : {
157 : 0 : QgsLayoutItem::attemptResize( size, includesFrame );
158 : : //update size of attached grid to reflect new page size and position
159 : 0 : mGrid->setRect( 0, 0, rect().width(), rect().height() );
160 : :
161 : 0 : mLayout->guides().update();
162 : 0 : }
163 : :
164 : 0 : void QgsLayoutItemPage::createDefaultPageStyleSymbol()
165 : : {
166 : 0 : QVariantMap properties;
167 : 0 : properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
168 : 0 : properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
169 : 0 : properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "no" ) );
170 : 0 : properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
171 : 0 : mPageStyleSymbol.reset( QgsFillSymbol::createSimple( properties ) );
172 : 0 : }
173 : :
174 : :
175 : :
176 : : ///@cond PRIVATE
177 : 0 : class QgsLayoutItemPageUndoCommand: public QgsLayoutItemUndoCommand
178 : : {
179 : : public:
180 : :
181 : 0 : QgsLayoutItemPageUndoCommand( QgsLayoutItemPage *page, const QString &text, int id = 0, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr )
182 : 0 : : QgsLayoutItemUndoCommand( page, text, id, parent )
183 : 0 : {}
184 : :
185 : 0 : void restoreState( QDomDocument &stateDoc ) override
186 : : {
187 : 0 : QgsLayoutItemUndoCommand::restoreState( stateDoc );
188 : 0 : layout()->pageCollection()->reflow();
189 : 0 : }
190 : :
191 : : protected:
192 : :
193 : 0 : QgsLayoutItem *recreateItem( int, QgsLayout *layout ) override
194 : : {
195 : 0 : QgsLayoutItemPage *page = new QgsLayoutItemPage( layout );
196 : 0 : layout->pageCollection()->addPage( page );
197 : 0 : return page;
198 : 0 : }
199 : : };
200 : : ///@endcond
201 : :
202 : 0 : QgsAbstractLayoutUndoCommand *QgsLayoutItemPage::createCommand( const QString &text, int id, QUndoCommand *parent )
203 : : {
204 : 0 : return new QgsLayoutItemPageUndoCommand( this, text, id, parent );
205 : 0 : }
206 : :
207 : 0 : QgsLayoutItem::ExportLayerBehavior QgsLayoutItemPage::exportLayerBehavior() const
208 : : {
209 : 0 : return CanGroupWithItemsOfSameType;
210 : : }
211 : :
212 : 0 : bool QgsLayoutItemPage::accept( QgsStyleEntityVisitorInterface *visitor ) const
213 : : {
214 : 0 : QgsStyleSymbolEntity entity( mPageStyleSymbol.get() );
215 : 0 : if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "page" ), QObject::tr( "Page" ) ) ) )
216 : 0 : return false;
217 : 0 : return true;
218 : 0 : }
219 : :
220 : 0 : void QgsLayoutItemPage::redraw()
221 : : {
222 : 0 : QgsLayoutItem::redraw();
223 : 0 : mGrid->update();
224 : 0 : }
225 : :
226 : 0 : void QgsLayoutItemPage::draw( QgsLayoutItemRenderContext &context )
227 : : {
228 : 0 : if ( !context.renderContext().painter() || !mLayout || !mLayout->renderContext().pagesVisible() )
229 : : {
230 : 0 : return;
231 : : }
232 : :
233 : 0 : double scale = context.renderContext().convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
234 : :
235 : 0 : QgsExpressionContext expressionContext = createExpressionContext();
236 : 0 : context.renderContext().setExpressionContext( expressionContext );
237 : :
238 : 0 : QPainter *painter = context.renderContext().painter();
239 : 0 : QgsScopedQPainterState painterState( painter );
240 : :
241 : 0 : 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 : 0 : painter->setRenderHint( QPainter::Antialiasing, false );
246 : :
247 : 0 : QRectF pageRect = QRectF( 0, 0, scale * rect().width(), scale * rect().height() );
248 : :
249 : : //shadow
250 : 0 : painter->setBrush( QBrush( QColor( 150, 150, 150 ) ) );
251 : 0 : painter->setPen( Qt::NoPen );
252 : 0 : painter->drawRect( pageRect.translated( std::min( scale * mLayout->pageCollection()->pageShadowWidth(), mMaximumShadowWidth ),
253 : 0 : std::min( scale * mLayout->pageCollection()->pageShadowWidth(), mMaximumShadowWidth ) ) );
254 : :
255 : : //page area
256 : 0 : painter->setBrush( QColor( 215, 215, 215 ) );
257 : 0 : QPen pagePen = QPen( QColor( 100, 100, 100 ), 0 );
258 : 0 : pagePen.setJoinStyle( Qt::MiterJoin );
259 : 0 : pagePen.setCosmetic( true );
260 : 0 : painter->setPen( pagePen );
261 : 0 : painter->drawRect( pageRect );
262 : 0 : }
263 : :
264 : 0 : if ( mPageStyleSymbol )
265 : : {
266 : 0 : std::unique_ptr< QgsFillSymbol > symbol( mPageStyleSymbol->clone() );
267 : 0 : symbol->startRender( context.renderContext() );
268 : :
269 : : //get max bleed from symbol
270 : 0 : 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 : 0 : if ( !mLayout->renderContext().isPreviewRender() || !qgsDoubleNear( maxBleedPixels, 0.0 ) )
276 : : {
277 : 0 : maxBleedPixels = std::floor( maxBleedPixels - 2 );
278 : 0 : }
279 : :
280 : : // round up
281 : 0 : QPolygonF pagePolygon = QPolygonF( QRectF( maxBleedPixels, maxBleedPixels,
282 : 0 : std::ceil( rect().width() * scale ) - 2 * maxBleedPixels, std::ceil( rect().height() * scale ) - 2 * maxBleedPixels ) );
283 : 0 : QVector<QPolygonF> rings; //empty list
284 : :
285 : 0 : symbol->renderPolygon( pagePolygon, &rings, nullptr, context.renderContext() );
286 : 0 : symbol->stopRender( context.renderContext() );
287 : 0 : }
288 : 0 : }
289 : :
290 : 0 : void QgsLayoutItemPage::drawFrame( QgsRenderContext & )
291 : 0 : {}
292 : :
293 : 0 : void QgsLayoutItemPage::drawBackground( QgsRenderContext & )
294 : 0 : {}
295 : :
296 : 0 : bool QgsLayoutItemPage::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
297 : : {
298 : 0 : QDomElement styleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mPageStyleSymbol.get(), document, context );
299 : 0 : element.appendChild( styleElem );
300 : : return true;
301 : 0 : }
302 : :
303 : 0 : bool QgsLayoutItemPage::readPropertiesFromElement( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext &context )
304 : : {
305 : 0 : QDomElement symbolElem = element.firstChildElement( QStringLiteral( "symbol" ) );
306 : 0 : if ( !symbolElem.isNull() )
307 : : {
308 : 0 : mPageStyleSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElem, context ) );
309 : 0 : }
310 : : else
311 : : {
312 : 0 : createDefaultPageStyleSymbol();
313 : : }
314 : :
315 : : return true;
316 : 0 : }
317 : :
318 : : //
319 : : // QgsLayoutItemPageGrid
320 : : //
321 : : ///@cond PRIVATE
322 : :
323 : 0 : QgsLayoutItemPageGrid::QgsLayoutItemPageGrid( double x, double y, double width, double height, QgsLayout *layout )
324 : 0 : : QGraphicsRectItem( 0, 0, width, height )
325 : 0 : , mLayout( layout )
326 : 0 : {
327 : : // needed to access current view transform during paint operations
328 : 0 : setFlags( flags() | QGraphicsItem::ItemUsesExtendedStyleOption );
329 : 0 : setCacheMode( QGraphicsItem::DeviceCoordinateCache );
330 : 0 : setFlag( QGraphicsItem::ItemIsSelectable, false );
331 : 0 : setFlag( QGraphicsItem::ItemIsMovable, false );
332 : 0 : setZValue( QgsLayout::ZGrid );
333 : 0 : setPos( x, y );
334 : 0 : }
335 : :
336 : 0 : void QgsLayoutItemPageGrid::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
337 : : {
338 : : Q_UNUSED( pWidget )
339 : :
340 : : //draw grid
341 : 0 : if ( !mLayout )
342 : 0 : return;
343 : :
344 : 0 : if ( !mLayout->renderContext().isPreviewRender() )
345 : 0 : return;
346 : :
347 : 0 : const QgsLayoutRenderContext &context = mLayout->renderContext();
348 : 0 : const QgsLayoutGridSettings &grid = mLayout->gridSettings();
349 : :
350 : 0 : if ( !context.gridVisible() || grid.resolution().length() <= 0 )
351 : 0 : return;
352 : :
353 : 0 : QPointF gridOffset = mLayout->convertToLayoutUnits( grid.offset() );
354 : 0 : double gridResolution = mLayout->convertToLayoutUnits( grid.resolution() );
355 : 0 : int gridMultiplyX = static_cast< int >( gridOffset.x() / gridResolution );
356 : 0 : int gridMultiplyY = static_cast< int >( gridOffset.y() / gridResolution );
357 : 0 : double currentXCoord = gridOffset.x() - gridMultiplyX * gridResolution;
358 : : double currentYCoord;
359 : 0 : double minYCoord = gridOffset.y() - gridMultiplyY * gridResolution;
360 : :
361 : 0 : QgsScopedQPainterState painterState( painter );
362 : : //turn of antialiasing so grid is nice and sharp
363 : 0 : painter->setRenderHint( QPainter::Antialiasing, false );
364 : :
365 : 0 : switch ( grid.style() )
366 : : {
367 : : case QgsLayoutGridSettings::StyleLines:
368 : : {
369 : 0 : painter->setPen( grid.pen() );
370 : :
371 : : //draw vertical lines
372 : 0 : for ( ; currentXCoord <= rect().width(); currentXCoord += gridResolution )
373 : : {
374 : 0 : painter->drawLine( QPointF( currentXCoord, 0 ), QPointF( currentXCoord, rect().height() ) );
375 : 0 : }
376 : :
377 : : //draw horizontal lines
378 : 0 : currentYCoord = minYCoord;
379 : 0 : for ( ; currentYCoord <= rect().height(); currentYCoord += gridResolution )
380 : : {
381 : 0 : painter->drawLine( QPointF( 0, currentYCoord ), QPointF( rect().width(), currentYCoord ) );
382 : 0 : }
383 : 0 : break;
384 : : }
385 : :
386 : : case QgsLayoutGridSettings::StyleDots:
387 : : case QgsLayoutGridSettings::StyleCrosses:
388 : : {
389 : 0 : QPen gridPen = grid.pen();
390 : 0 : painter->setPen( gridPen );
391 : 0 : painter->setBrush( QBrush( gridPen.color() ) );
392 : 0 : double halfCrossLength = 1;
393 : 0 : 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 : 0 : halfCrossLength = 1 / QgsLayoutUtils::scaleFactorFromItemStyle( itemStyle );
398 : 0 : }
399 : : else
400 : : {
401 : 0 : halfCrossLength = gridResolution / 6;
402 : : }
403 : :
404 : 0 : for ( ; currentXCoord <= rect().width(); currentXCoord += gridResolution )
405 : : {
406 : 0 : currentYCoord = minYCoord;
407 : 0 : for ( ; currentYCoord <= rect().height(); currentYCoord += gridResolution )
408 : : {
409 : 0 : painter->drawLine( QPointF( currentXCoord - halfCrossLength, currentYCoord ), QPointF( currentXCoord + halfCrossLength, currentYCoord ) );
410 : 0 : painter->drawLine( QPointF( currentXCoord, currentYCoord - halfCrossLength ), QPointF( currentXCoord, currentYCoord + halfCrossLength ) );
411 : 0 : }
412 : 0 : }
413 : : break;
414 : 0 : }
415 : : }
416 : 0 : }
417 : :
418 : : ///@endcond
|