[layouts][needs-docs] Move page background setting to a per-page option

The UI for this setting was sitting under the page properties panel,
which led users to believe it was a per-page setting (rather than
applying to ALL pages in the layout).

Instead, move this property to sit within individual layout item pages
so that the behavior matches what the UI suggests.

Fixes #25695
This commit is contained in:
Nyall Dawson 2019-09-27 12:13:27 +10:00
parent e534463719
commit 45e847ef69
10 changed files with 227 additions and 44 deletions

View File

@ -38,6 +38,7 @@ Item representing the paper in a layout.
%Docstring
Constructor for QgsLayoutItemPage, with the specified parent ``layout``.
%End
~QgsLayoutItemPage();
static QgsLayoutItemPage *create( QgsLayout *layout ) /Factory/;
%Docstring
@ -86,6 +87,26 @@ Returns the page orientation.
There is no direct setter for page orientation - use setPageSize() instead.
%End
void setPageStyleSymbol( QgsFillSymbol *symbol /Transfer/ );
%Docstring
Sets the ``symbol`` to use for drawing the page background.
Ownership of ``symbol`` is transferred to the page.
.. seealso:: :py:func:`pageStyleSymbol`
.. versionadded:: 3.10
%End
const QgsFillSymbol *pageStyleSymbol() const;
%Docstring
Returns the symbol to use for drawing the page background.
.. seealso:: :py:func:`setPageStyleSymbol`
.. versionadded:: 3.10
%End
static QgsLayoutItemPage::Orientation decodePageOrientation( const QString &string, bool *ok /Out/ = 0 );
%Docstring
Decodes a ``string`` representing a page orientation. If specified, ``ok``
@ -101,6 +122,8 @@ page orientation.
virtual ExportLayerBehavior exportLayerBehavior() const;
virtual bool accept( QgsStyleEntityVisitorInterface *visitor ) const;
public slots:
@ -115,6 +138,10 @@ page orientation.
virtual void drawBackground( QgsRenderContext &context );
virtual bool writePropertiesToElement( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const;
virtual bool readPropertiesFromElement( const QDomElement &itemElement, const QDomDocument &document, const QgsReadWriteContext &context );
};

View File

@ -195,11 +195,13 @@ Ownership is not transferred, and a copy of the symbol is created internally.
.. seealso:: :py:func:`pageStyleSymbol`
%End
const QgsFillSymbol *pageStyleSymbol() const;
const QgsFillSymbol *pageStyleSymbol() const /Deprecated/;
%Docstring
Returns the symbol to use for drawing pages in the collection.
.. seealso:: :py:func:`setPageStyleSymbol`
.. deprecated:: Use QgsLayoutItemPage.pageStyleSymbol() instead.
%End
void beginPageSizeChange();

View File

@ -53,7 +53,7 @@ QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, Q
mLockAspectRatio->setHeightSpinBox( mHeightSpin );
mSymbolButton->setSymbolType( QgsSymbol::Fill );
mSymbolButton->setSymbol( mPage->layout()->pageCollection()->pageStyleSymbol()->clone() );
mSymbolButton->setSymbol( mPage->pageStyleSymbol()->clone() );
connect( mPageSizeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutPagePropertiesWidget::pageSizeChanged );
connect( mPageOrientationComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutPagePropertiesWidget::orientationChanged );
@ -183,7 +183,7 @@ void QgsLayoutPagePropertiesWidget::setToCustomSize()
void QgsLayoutPagePropertiesWidget::symbolChanged()
{
mPage->layout()->undoStack()->beginCommand( mPage->layout()->pageCollection(), tr( "Change Page Background" ), QgsLayoutItemPage::UndoPageSymbol );
mPage->layout()->pageCollection()->setPageStyleSymbol( static_cast< QgsFillSymbol * >( mSymbolButton->symbol() )->clone() );
mPage->setPageStyleSymbol( static_cast< QgsFillSymbol * >( mSymbolButton->symbol() )->clone() );
mPage->layout()->undoStack()->endCommand();
}

View File

@ -139,17 +139,6 @@ std::unique_ptr< QgsPrintLayout > QgsCompositionConverter::createLayoutFromCompo
float paperHeight = composerElement.attribute( QStringLiteral( "paperHeight" ) ).toDouble( );
float paperWidth = composerElement.attribute( QStringLiteral( "paperWidth" ) ).toDouble( );
if ( composerElement.elementsByTagName( QStringLiteral( "symbol" ) ).size() )
{
QDomElement symbolElement = composerElement.elementsByTagName( QStringLiteral( "symbol" ) ).at( 0 ).toElement();
QgsReadWriteContext context;
if ( project )
context.setPathResolver( project->pathResolver() );
std::unique_ptr< QgsFillSymbol > symbol( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElement, context ) );
if ( symbol )
layout->pageCollection()->setPageStyleSymbol( symbol.get() );
}
QString name = composerElement.attribute( QStringLiteral( "name" ) );
// Try title
if ( name.isEmpty() )
@ -179,6 +168,19 @@ std::unique_ptr< QgsPrintLayout > QgsCompositionConverter::createLayoutFromCompo
layout->guides().addGuide( guide.release() );
}
}
if ( composerElement.elementsByTagName( QStringLiteral( "symbol" ) ).size() )
{
QDomElement symbolElement = composerElement.elementsByTagName( QStringLiteral( "symbol" ) ).at( 0 ).toElement();
QgsReadWriteContext context;
if ( project )
context.setPathResolver( project->pathResolver() );
std::unique_ptr< QgsFillSymbol > symbol( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElement, context ) );
if ( symbol )
layout->pageCollection()->setPageStyleSymbol( symbol.get() );
}
addItemsFromCompositionXml( layout.get(), composerElement );
// Read atlas from the parent element (Composer)

View File

@ -797,14 +797,6 @@ bool QgsLayout::accept( QgsStyleEntityVisitorInterface *visitor ) const
if ( !layoutItem->accept( visitor ) )
return false;
}
if ( pageCollection()->pageStyleSymbol() )
{
QgsStyleSymbolEntity entity( pageCollection()->pageStyleSymbol() );
if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "page" ), QObject::tr( "Page" ) ) ) )
return false;
}
return true;
}

View File

@ -22,6 +22,8 @@
#include "qgslayoutitemundocommand.h"
#include "qgslayoutpagecollection.h"
#include "qgslayoutundostack.h"
#include "qgsstyle.h"
#include "qgsstyleentityvisitor.h"
#include <QPainter>
#include <QStyleOptionGraphicsItem>
@ -44,8 +46,12 @@ QgsLayoutItemPage::QgsLayoutItemPage( QgsLayout *layout )
mGrid.reset( new QgsLayoutItemPageGrid( pos().x(), pos().y(), rect().width(), rect().height(), mLayout ) );
mGrid->setParentItem( this );
createDefaultPageStyleSymbol();
}
QgsLayoutItemPage::~QgsLayoutItemPage() = default;
QgsLayoutItemPage *QgsLayoutItemPage::create( QgsLayout *layout )
{
return new QgsLayoutItemPage( layout );
@ -108,6 +114,12 @@ QgsLayoutItemPage::Orientation QgsLayoutItemPage::orientation() const
return Portrait;
}
void QgsLayoutItemPage::setPageStyleSymbol( QgsFillSymbol *symbol )
{
mPageStyleSymbol.reset( symbol );
update();
}
QgsLayoutItemPage::Orientation QgsLayoutItemPage::decodePageOrientation( const QString &string, bool *ok )
{
if ( ok )
@ -149,6 +161,18 @@ void QgsLayoutItemPage::attemptResize( const QgsLayoutSize &size, bool includesF
mLayout->guides().update();
}
void QgsLayoutItemPage::createDefaultPageStyleSymbol()
{
QgsStringMap properties;
properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "no" ) );
properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
mPageStyleSymbol.reset( QgsFillSymbol::createSimple( properties ) );
}
///@cond PRIVATE
class QgsLayoutItemPageUndoCommand: public QgsLayoutItemUndoCommand
{
@ -185,6 +209,14 @@ QgsLayoutItem::ExportLayerBehavior QgsLayoutItemPage::exportLayerBehavior() cons
return CanGroupWithItemsOfSameType;
}
bool QgsLayoutItemPage::accept( QgsStyleEntityVisitorInterface *visitor ) const
{
QgsStyleSymbolEntity entity( pageStyleSymbol() );
if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "page" ), QObject::tr( "Page" ) ) ) )
return false;
return true;
}
void QgsLayoutItemPage::redraw()
{
QgsLayoutItem::redraw();
@ -229,28 +261,31 @@ void QgsLayoutItemPage::draw( QgsLayoutItemRenderContext &context )
painter->drawRect( pageRect );
}
std::unique_ptr< QgsFillSymbol > symbol( mLayout->pageCollection()->pageStyleSymbol()->clone() );
symbol->startRender( context.renderContext() );
//get max bleed from symbol
double maxBleedPixels = QgsSymbolLayerUtils::estimateMaxSymbolBleed( symbol.get(), context.renderContext() );
//Now subtract 1 pixel to prevent semi-transparent borders at edge of solid page caused by
//anti-aliased painting. This may cause a pixel to be cropped from certain edge lines/symbols,
//but that can be counteracted by adding a dummy transparent line symbol layer with a wider line width
if ( !mLayout->renderContext().isPreviewRender() || !qgsDoubleNear( maxBleedPixels, 0.0 ) )
if ( mPageStyleSymbol )
{
maxBleedPixels = std::floor( maxBleedPixels - 2 );
std::unique_ptr< QgsFillSymbol > symbol( mPageStyleSymbol->clone() );
symbol->startRender( context.renderContext() );
//get max bleed from symbol
double maxBleedPixels = QgsSymbolLayerUtils::estimateMaxSymbolBleed( symbol.get(), context.renderContext() );
//Now subtract 1 pixel to prevent semi-transparent borders at edge of solid page caused by
//anti-aliased painting. This may cause a pixel to be cropped from certain edge lines/symbols,
//but that can be counteracted by adding a dummy transparent line symbol layer with a wider line width
if ( !mLayout->renderContext().isPreviewRender() || !qgsDoubleNear( maxBleedPixels, 0.0 ) )
{
maxBleedPixels = std::floor( maxBleedPixels - 2 );
}
// round up
QPolygonF pagePolygon = QPolygonF( QRectF( maxBleedPixels, maxBleedPixels,
std::ceil( rect().width() * scale ) - 2 * maxBleedPixels, std::ceil( rect().height() * scale ) - 2 * maxBleedPixels ) );
QList<QPolygonF> rings; //empty list
symbol->renderPolygon( pagePolygon, &rings, nullptr, context.renderContext() );
symbol->stopRender( context.renderContext() );
}
// round up
QPolygonF pagePolygon = QPolygonF( QRectF( maxBleedPixels, maxBleedPixels,
std::ceil( rect().width() * scale ) - 2 * maxBleedPixels, std::ceil( rect().height() * scale ) - 2 * maxBleedPixels ) );
QList<QPolygonF> rings; //empty list
symbol->renderPolygon( pagePolygon, &rings, nullptr, context.renderContext() );
symbol->stopRender( context.renderContext() );
painter->restore();
}
@ -260,6 +295,28 @@ void QgsLayoutItemPage::drawFrame( QgsRenderContext & )
void QgsLayoutItemPage::drawBackground( QgsRenderContext & )
{}
bool QgsLayoutItemPage::writePropertiesToElement( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const
{
QDomElement styleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mPageStyleSymbol.get(), document, context );
element.appendChild( styleElem );
return true;
}
bool QgsLayoutItemPage::readPropertiesFromElement( const QDomElement &element, const QDomDocument &, const QgsReadWriteContext &context )
{
QDomElement symbolElem = element.firstChildElement( QStringLiteral( "symbol" ) );
if ( !symbolElem.isNull() )
{
mPageStyleSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElem, context ) );
}
else
{
createDefaultPageStyleSymbol();
}
return true;
}
//
// QgsLayoutItemPageGrid
//

View File

@ -75,6 +75,7 @@ class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem
* Constructor for QgsLayoutItemPage, with the specified parent \a layout.
*/
explicit QgsLayoutItemPage( QgsLayout *layout );
~QgsLayoutItemPage() override;
/**
* Returns a new page item for the specified \a layout.
@ -115,6 +116,26 @@ class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem
*/
Orientation orientation() const;
/**
* Sets the \a symbol to use for drawing the page background.
*
* Ownership of \a symbol is transferred to the page.
*
* \see pageStyleSymbol()
*
* \since QGIS 3.10
*/
void setPageStyleSymbol( QgsFillSymbol *symbol SIP_TRANSFER );
/**
* Returns the symbol to use for drawing the page background.
*
* \see setPageStyleSymbol()
*
* \since QGIS 3.10
*/
const QgsFillSymbol *pageStyleSymbol() const { return mPageStyleSymbol.get(); }
/**
* Decodes a \a string representing a page orientation. If specified, \a ok
* will be set to TRUE if string could be successfully interpreted as a
@ -126,6 +147,7 @@ class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem
void attemptResize( const QgsLayoutSize &size, bool includesFrame = false ) override;
QgsAbstractLayoutUndoCommand *createCommand( const QString &text, int id, QUndoCommand *parent = nullptr ) override SIP_FACTORY;
ExportLayerBehavior exportLayerBehavior() const override;
bool accept( QgsStyleEntityVisitorInterface *visitor ) const override;
public slots:
@ -136,6 +158,8 @@ class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem
void draw( QgsLayoutItemRenderContext &context ) override;
void drawFrame( QgsRenderContext &context ) override;
void drawBackground( QgsRenderContext &context ) override;
bool writePropertiesToElement( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context ) const override;
bool readPropertiesFromElement( const QDomElement &itemElement, const QDomDocument &document, const QgsReadWriteContext &context ) override;
private:
@ -144,6 +168,11 @@ class CORE_EXPORT QgsLayoutItemPage : public QgsLayoutItem
std::unique_ptr< QgsLayoutItemPageGrid > mGrid;
mutable QRectF mBoundingRect;
//! Symbol for drawing page
std::unique_ptr< QgsFillSymbol > mPageStyleSymbol;
void createDefaultPageStyleSymbol();
friend class TestQgsLayoutPage;
};

View File

@ -50,9 +50,14 @@ void QgsLayoutPageCollection::setPageStyleSymbol( QgsFillSymbol *symbol )
for ( QgsLayoutItemPage *page : qgis::as_const( mPages ) )
{
page->setPageStyleSymbol( symbol->clone() );
page->update();
}
}
const QgsFillSymbol *QgsLayoutPageCollection::pageStyleSymbol() const
{
return mPageStyleSymbol.get();
}
void QgsLayoutPageCollection::beginPageSizeChange()
@ -402,6 +407,8 @@ bool QgsLayoutPageCollection::readXml( const QDomElement &e, const QDomDocument
{
QDomElement pageElement = pageList.at( i ).toElement();
std::unique_ptr< QgsLayoutItemPage > page( new QgsLayoutItemPage( mLayout ) );
if ( mPageStyleSymbol )
page->setPageStyleSymbol( mPageStyleSymbol->clone() );
page->readXml( pageElement, document, context );
page->finalizeRestoreFromXml();
mPages.append( page.get() );
@ -722,4 +729,3 @@ void QgsLayoutPageCollection::createDefaultPageStyleSymbol()
mPageStyleSymbol.reset( QgsFillSymbol::createSimple( properties ) );
}

View File

@ -232,8 +232,10 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject, public QgsLayoutSeri
/**
* Returns the symbol to use for drawing pages in the collection.
* \see setPageStyleSymbol()
*
* \deprecated Use QgsLayoutItemPage::pageStyleSymbol() instead.
*/
const QgsFillSymbol *pageStyleSymbol() const { return mPageStyleSymbol.get(); }
Q_DECL_DEPRECATED const QgsFillSymbol *pageStyleSymbol() const SIP_DEPRECATED;
/**
* Should be called before changing any page item sizes, and followed by a call to

View File

@ -13,9 +13,16 @@ __copyright__ = 'Copyright 2017, The QGIS Project'
import qgis # NOQA
from qgis.testing import start_app, unittest
from qgis.core import QgsLayoutItemPage
from qgis.PyQt.QtCore import Qt
from qgis.core import (QgsProject,
QgsLayout,
QgsLayoutItemPage,
QgsSimpleFillSymbolLayer,
QgsFillSymbol,
QgsReadWriteContext)
from test_qgslayoutitem import LayoutItemTestCase
from qgis.PyQt.QtXml import QDomDocument
start_app()
@ -26,6 +33,65 @@ class TestQgsLayoutPage(unittest.TestCase, LayoutItemTestCase):
def setUpClass(cls):
cls.item_class = QgsLayoutItemPage
def testDefaults(self):
p = QgsProject()
l = QgsLayout(p)
p = QgsLayoutItemPage(l)
self.assertTrue(p.pageStyleSymbol())
fill = QgsSimpleFillSymbolLayer()
fill_symbol = QgsFillSymbol()
fill_symbol.changeSymbolLayer(0, fill)
fill.setColor(Qt.green)
fill.setStrokeColor(Qt.red)
fill.setStrokeWidth(6)
p.setPageStyleSymbol(fill_symbol)
self.assertEqual(p.pageStyleSymbol().symbolLayer(0).color().name(), '#00ff00')
self.assertEqual(p.pageStyleSymbol().symbolLayer(0).strokeColor().name(), '#ff0000')
def testReadWriteSettings(self):
p = QgsProject()
l = QgsLayout(p)
collection = l.pageCollection()
fill = QgsSimpleFillSymbolLayer()
fill_symbol = QgsFillSymbol()
fill_symbol.changeSymbolLayer(0, fill)
fill.setColor(Qt.green)
fill.setStrokeColor(Qt.red)
fill.setStrokeWidth(6)
page = QgsLayoutItemPage(l)
page.setPageSize('A4')
page.setPageStyleSymbol(fill_symbol.clone())
self.assertEqual(collection.pageNumber(page), -1)
collection.addPage(page)
# add a second page
page2 = QgsLayoutItemPage(l)
page2.setPageSize('A5')
fill_symbol.setColor(Qt.blue)
page2.setPageStyleSymbol(fill_symbol.clone())
collection.addPage(page2)
doc = QDomDocument("testdoc")
elem = doc.createElement("test")
self.assertTrue(collection.writeXml(elem, doc, QgsReadWriteContext()))
l2 = QgsLayout(p)
collection2 = l2.pageCollection()
self.assertTrue(collection2.readXml(elem.firstChildElement(), doc, QgsReadWriteContext()))
self.assertEqual(collection2.pageCount(), 2)
self.assertEqual(collection2.page(0).pageStyleSymbol().symbolLayer(0).color().name(), '#00ff00')
self.assertEqual(collection2.page(0).pageStyleSymbol().symbolLayer(0).strokeColor().name(), '#ff0000')
self.assertEqual(collection2.page(1).pageStyleSymbol().symbolLayer(0).color().name(), '#0000ff')
self.assertEqual(collection2.page(1).pageStyleSymbol().symbolLayer(0).strokeColor().name(), '#ff0000')
if __name__ == '__main__':
unittest.main()