From f649f1f8a7379212aedf21f604b8e480ee621719 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 15 Dec 2017 15:10:11 +1000 Subject: [PATCH] [layouts][needs-docs] Add control for whether pages should be exported, including data defined setting This replaces the 2.x data-defined "number of pages" setting. Instead of requiring users to develop an expression to return the number of pages, instead we allow individual pages to have a data defined control of whether that page should be included in the export. This is more flexible, and works correctly with the mixed page size model for layouts. --- python/core/layout/qgslayout.sip | 3 - src/app/layout/qgslayoutdesignerdialog.cpp | 6 ++ .../layout/qgslayoutpagepropertieswidget.cpp | 11 +++ .../layout/qgslayoutpagepropertieswidget.h | 1 + src/core/layout/qgslayout.h | 3 - src/core/layout/qgslayoutcontext.h | 3 +- src/core/layout/qgslayoutexporter.cpp | 43 +++++++++- src/core/layout/qgslayoutitem.h | 10 +-- src/core/layout/qgslayoutpagecollection.cpp | 4 + .../layout/qgslayoutpagepropertieswidget.ui | 82 ++++++++++++++----- src/ui/qgisapp.ui | 2 +- tests/src/core/testqgslayout.cpp | 28 +++++++ tests/src/python/test_qgslayoutexporter.py | 46 +++++++++++ 13 files changed, 204 insertions(+), 38 deletions(-) diff --git a/python/core/layout/qgslayout.sip b/python/core/layout/qgslayout.sip index a94ad469e69..eb3e5cd1203 100644 --- a/python/core/layout/qgslayout.sip +++ b/python/core/layout/qgslayout.sip @@ -580,9 +580,6 @@ Emitted when the layout's name is changed. }; - - - /************************************************************************ * This file has been generated automatically from * * * diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index f50aa54ae1c..7dce81cbc66 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -1511,6 +1511,9 @@ void QgsLayoutDesignerDialog::exportToRaster() mView->setPaintingEnabled( false ); QApplication::setOverrideCursor( Qt::BusyCursor ); + // force a refresh, to e.g. update data defined properties, tables, etc + mLayout->refresh(); + QgsLayoutExporter exporter( mLayout ); QgsLayoutExporter::ImageExportSettings settings; @@ -1624,6 +1627,9 @@ void QgsLayoutDesignerDialog::exportToPdf() pdfSettings.rasterizeWholeImage = mLayout->customProperty( QStringLiteral( "rasterise" ), false ).toBool(); pdfSettings.forceVectorOutput = mLayout->customProperty( QStringLiteral( "forceVector" ), false ).toBool(); + // force a refresh, to e.g. update data defined properties, tables, etc + mLayout->refresh(); + QgsLayoutExporter exporter( mLayout ); switch ( exporter.exportToPdf( outputFileName, pdfSettings ) ) { diff --git a/src/app/layout/qgslayoutpagepropertieswidget.cpp b/src/app/layout/qgslayoutpagepropertieswidget.cpp index 3518998222d..22150b9d693 100644 --- a/src/app/layout/qgslayoutpagepropertieswidget.cpp +++ b/src/app/layout/qgslayoutpagepropertieswidget.cpp @@ -39,6 +39,7 @@ QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, Q mWidthSpin->setValue( mPage->pageSize().width() ); mHeightSpin->setValue( mPage->pageSize().height() ); mSizeUnitsComboBox->setUnit( mPage->pageSize().units() ); + mExcludePageCheckBox->setChecked( mPage->excludeFromExports() ); mPageOrientationComboBox->setCurrentIndex( mPageOrientationComboBox->findData( mPage->orientation() ) ); @@ -59,11 +60,14 @@ QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, Q connect( mHeightSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPagePropertiesWidget::updatePageSize ); connect( mWidthSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPagePropertiesWidget::setToCustomSize ); connect( mHeightSpin, static_cast< void ( QDoubleSpinBox::* )( double )>( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPagePropertiesWidget::setToCustomSize ); + connect( mExcludePageCheckBox, &QCheckBox::toggled, this, &QgsLayoutPagePropertiesWidget::excludeExportsToggled ); connect( mSymbolButton, &QgsSymbolButton::changed, this, &QgsLayoutPagePropertiesWidget::symbolChanged ); registerDataDefinedButton( mPaperSizeDDBtn, QgsLayoutObject::PresetPaperSize ); registerDataDefinedButton( mWidthDDBtn, QgsLayoutObject::ItemWidth ); registerDataDefinedButton( mHeightDDBtn, QgsLayoutObject::ItemHeight ); + registerDataDefinedButton( mExcludePageDDBtn, QgsLayoutObject::ExcludeFromExports ); + mExcludePageDDBtn->registerEnabledWidget( mExcludePageCheckBox, false ); showCurrentPageSize(); } @@ -155,6 +159,13 @@ void QgsLayoutPagePropertiesWidget::symbolChanged() mPage->layout()->undoStack()->endCommand(); } +void QgsLayoutPagePropertiesWidget::excludeExportsToggled( bool checked ) +{ + mPage->beginCommand( !checked ? tr( "Include Page in Exports" ) : tr( "Exclude Page from Exports" ) ); + mPage->setExcludeFromExports( checked ); + mPage->endCommand(); +} + void QgsLayoutPagePropertiesWidget::showCurrentPageSize() { QgsLayoutSize paperSize = mPage->pageSize(); diff --git a/src/app/layout/qgslayoutpagepropertieswidget.h b/src/app/layout/qgslayoutpagepropertieswidget.h index bb25f7ed6ad..195b9ed4d27 100644 --- a/src/app/layout/qgslayoutpagepropertieswidget.h +++ b/src/app/layout/qgslayoutpagepropertieswidget.h @@ -48,6 +48,7 @@ class QgsLayoutPagePropertiesWidget : public QgsLayoutItemBaseWidget, private Ui void updatePageSize(); void setToCustomSize(); void symbolChanged(); + void excludeExportsToggled( bool checked ); private: diff --git a/src/core/layout/qgslayout.h b/src/core/layout/qgslayout.h index 6bbcc7ac991..737f595de3e 100644 --- a/src/core/layout/qgslayout.h +++ b/src/core/layout/qgslayout.h @@ -672,6 +672,3 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext }; #endif //QGSLAYOUT_H - - - diff --git a/src/core/layout/qgslayoutcontext.h b/src/core/layout/qgslayoutcontext.h index 6b9028302d3..cfbfcf703b0 100644 --- a/src/core/layout/qgslayoutcontext.h +++ b/src/core/layout/qgslayoutcontext.h @@ -236,7 +236,8 @@ class CORE_EXPORT QgsLayoutContext : public QObject bool mPagesVisible = true; friend class QgsLayoutExporter; - friend class LayoutItemCacheSettingRestorer; + friend class TestQgsLayout; + friend class LayoutContextPreviewSettingRestorer; }; diff --git a/src/core/layout/qgslayoutexporter.cpp b/src/core/layout/qgslayoutexporter.cpp index 7188494e7c9..9eafe04db21 100644 --- a/src/core/layout/qgslayoutexporter.cpp +++ b/src/core/layout/qgslayoutexporter.cpp @@ -26,6 +26,30 @@ #include "gdal.h" #include "cpl_conv.h" +///@cond PRIVATE +class LayoutContextPreviewSettingRestorer +{ + public: + + LayoutContextPreviewSettingRestorer( QgsLayout *layout ) + : mLayout( layout ) + , mPreviousSetting( layout->context().mIsPreviewRender ) + { + mLayout->context().mIsPreviewRender = false; + } + + ~LayoutContextPreviewSettingRestorer() + { + mLayout->context().mIsPreviewRender = mPreviousSetting; + } + + private: + QgsLayout *mLayout = nullptr; + bool mPreviousSetting = false; +}; + +///@endcond PRIVATE + QgsLayoutExporter::QgsLayoutExporter( QgsLayout *layout ) : mLayout( layout ) { @@ -53,6 +77,9 @@ void QgsLayoutExporter::renderPage( QPainter *painter, int page ) const return; } + LayoutContextPreviewSettingRestorer restorer( mLayout ); + ( void )restorer; + QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() ); renderRegion( painter, paperRect ); } @@ -73,6 +100,9 @@ QImage QgsLayoutExporter::renderPageToImage( int page, QSize imageSize, double d return QImage(); } + LayoutContextPreviewSettingRestorer restorer( mLayout ); + ( void )restorer; + QRectF paperRect = QRectF( pageItem->pos().x(), pageItem->pos().y(), pageItem->rect().width(), pageItem->rect().height() ); return renderRegionToImage( paperRect, imageSize, dpi ); } @@ -85,8 +115,6 @@ class LayoutItemCacheSettingRestorer LayoutItemCacheSettingRestorer( QgsLayout *layout ) : mLayout( layout ) { - mLayout->context().mIsPreviewRender = false; - const QList< QGraphicsItem * > items = mLayout->items(); for ( QGraphicsItem *item : items ) { @@ -101,8 +129,6 @@ class LayoutItemCacheSettingRestorer { it.key()->setCacheMode( it.value() ); } - - mLayout->context().mIsPreviewRender = true; } private: @@ -122,6 +148,8 @@ void QgsLayoutExporter::renderRegion( QPainter *painter, const QRectF ®ion ) LayoutItemCacheSettingRestorer cacheRestorer( mLayout ); ( void )cacheRestorer; + LayoutContextPreviewSettingRestorer restorer( mLayout ); + ( void )restorer; #if 0 //TODO setSnapLinesVisible( false ); @@ -138,6 +166,9 @@ void QgsLayoutExporter::renderRegion( QPainter *painter, const QRectF ®ion ) QImage QgsLayoutExporter::renderRegionToImage( const QRectF ®ion, QSize imageSize, double dpi ) const { + LayoutContextPreviewSettingRestorer restorer( mLayout ); + ( void )restorer; + double resolution = mLayout->context().dpi(); double oneInchInLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) ); if ( imageSize.isValid() ) @@ -219,6 +250,8 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToImage( const QString pageDetails.baseName = fi.baseName(); pageDetails.extension = fi.completeSuffix(); + LayoutContextPreviewSettingRestorer restorer( mLayout ); + ( void )restorer; LayoutContextSettingsRestorer dpiRestorer( mLayout ); ( void )dpiRestorer; mLayout->context().setDpi( settings.dpi ); @@ -303,6 +336,8 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToPdf( const QString &f mErrorFileName.clear(); + LayoutContextPreviewSettingRestorer restorer( mLayout ); + ( void )restorer; LayoutContextSettingsRestorer contextRestorer( mLayout ); ( void )contextRestorer; mLayout->context().setDpi( settings.dpi ); diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index 8c75460d8ef..3d59f5a831e 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -831,6 +831,11 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt */ void cancelCommand(); + /** + * Returns whether the item should be drawn in the current context. + */ + bool shouldDrawItem() const; + public slots: /** @@ -1049,11 +1054,6 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt */ virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context ); - /** - * Returns whether the item should be drawn in the current context. - */ - bool shouldDrawItem() const; - /** * Applies any present data defined size overrides to the specified layout \a size. */ diff --git a/src/core/layout/qgslayoutpagecollection.cpp b/src/core/layout/qgslayoutpagecollection.cpp index be7ace29d33..14b8e5ad982 100644 --- a/src/core/layout/qgslayoutpagecollection.cpp +++ b/src/core/layout/qgslayoutpagecollection.cpp @@ -482,6 +482,10 @@ bool QgsLayoutPageCollection::shouldExportPage( int page ) const return false; } + QgsLayoutItemPage *pageItem = mPages.at( page ); + if ( !pageItem->shouldDrawItem() ) + return false; + //check all frame items on page QList frames; itemsOnPage( frames, page ); diff --git a/src/ui/layout/qgslayoutpagepropertieswidget.ui b/src/ui/layout/qgslayoutpagepropertieswidget.ui index 7b808e3f568..b34aa1dc20b 100644 --- a/src/ui/layout/qgslayoutpagepropertieswidget.ui +++ b/src/ui/layout/qgslayoutpagepropertieswidget.ui @@ -14,27 +14,7 @@ New Item Properties - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Background - - - - + true @@ -56,6 +36,13 @@ + + + + Background + + + @@ -204,6 +191,56 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + If checked, this page will not be included when exporting the layout + + + Exclude page from exports + + + false + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -244,6 +281,9 @@ mHeightSpin mHeightDDBtn mSizeUnitsComboBox + mExcludePageCheckBox + mExcludePageDDBtn + mSymbolButton diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui index cc605c86a63..bc9f780fe16 100644 --- a/src/ui/qgisapp.ui +++ b/src/ui/qgisapp.ui @@ -17,7 +17,7 @@ 0 0 1018 - 20 + 25 diff --git a/tests/src/core/testqgslayout.cpp b/tests/src/core/testqgslayout.cpp index fd10006d8b0..5f993f11e50 100644 --- a/tests/src/core/testqgslayout.cpp +++ b/tests/src/core/testqgslayout.cpp @@ -613,6 +613,7 @@ void TestQgsLayout::shouldExportPage() QgsLayoutItemPage *page2 = new QgsLayoutItemPage( &l ); page2->setPageSize( "A4" ); l.pageCollection()->addPage( page2 ); + l.context().mIsPreviewRender = false; QgsLayoutItemHtml *htmlItem = new QgsLayoutItemHtml( &l ); //frame on page 1 @@ -645,6 +646,33 @@ void TestQgsLayout::shouldExportPage() QVERIFY( l.pageCollection()->shouldExportPage( 0 ) ); QVERIFY( !l.pageCollection()->shouldExportPage( 1 ) ); + + // get rid of frames + l.removeItem( frame1 ); + l.removeItem( frame2 ); + l.removeMultiFrame( htmlItem ); + delete htmlItem; + QgsApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete ); + + QVERIFY( l.pageCollection()->shouldExportPage( 0 ) ); + QVERIFY( l.pageCollection()->shouldExportPage( 1 ) ); + + // explicitly set exclude from exports + l.pageCollection()->page( 0 )->setExcludeFromExports( true ); + QVERIFY( !l.pageCollection()->shouldExportPage( 0 ) ); + QVERIFY( l.pageCollection()->shouldExportPage( 1 ) ); + + l.pageCollection()->page( 0 )->setExcludeFromExports( false ); + l.pageCollection()->page( 1 )->setExcludeFromExports( true ); + QVERIFY( l.pageCollection()->shouldExportPage( 0 ) ); + QVERIFY( !l.pageCollection()->shouldExportPage( 1 ) ); + + l.pageCollection()->page( 1 )->setExcludeFromExports( false ); + l.pageCollection()->page( 0 )->dataDefinedProperties().setProperty( QgsLayoutObject::ExcludeFromExports, QgsProperty::fromExpression( "1" ) ); + l.pageCollection()->page( 1 )->dataDefinedProperties().setProperty( QgsLayoutObject::ExcludeFromExports, QgsProperty::fromValue( true ) ); + l.refresh(); + QVERIFY( !l.pageCollection()->shouldExportPage( 0 ) ); + QVERIFY( !l.pageCollection()->shouldExportPage( 1 ) ); } void TestQgsLayout::pageIsEmpty() diff --git a/tests/src/python/test_qgslayoutexporter.py b/tests/src/python/test_qgslayoutexporter.py index fcb95b40f83..5f7063b7443 100644 --- a/tests/src/python/test_qgslayoutexporter.py +++ b/tests/src/python/test_qgslayoutexporter.py @@ -317,6 +317,52 @@ class TestQgsLayoutExporter(unittest.TestCase): self.assertAlmostEqual(values[4], 1925.000000000000, 2) self.assertAlmostEqual(values[5], 3050.000000000000, 2) + def testExcludePagesImage(self): + l = QgsLayout(QgsProject.instance()) + l.initializeDefaults() + # add a second page + page2 = QgsLayoutItemPage(l) + page2.setPageSize('A5') + l.pageCollection().addPage(page2) + + exporter = QgsLayoutExporter(l) + # setup settings + settings = QgsLayoutExporter.ImageExportSettings() + settings.dpi = 80 + settings.generateWorldFile = False + + rendered_file_path = os.path.join(self.basetestpath, 'test_exclude_export.png') + details = QgsLayoutExporter.PageExportDetails() + details.directory = self.basetestpath + details.baseName = 'test_exclude_export' + details.extension = 'png' + details.page = 0 + + self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success) + self.assertTrue(os.path.exists(exporter.generateFileName(details))) + details.page = 1 + self.assertTrue(os.path.exists(exporter.generateFileName(details))) + + # exclude a page + l.pageCollection().page(0).setExcludeFromExports(True) + rendered_file_path = os.path.join(self.basetestpath, 'test_exclude_export_excluded.png') + details.baseName = 'test_exclude_export_excluded' + details.page = 0 + self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success) + self.assertFalse(os.path.exists(exporter.generateFileName(details))) + details.page = 1 + self.assertTrue(os.path.exists(exporter.generateFileName(details))) + + # exclude second page + l.pageCollection().page(1).setExcludeFromExports(True) + rendered_file_path = os.path.join(self.basetestpath, 'test_exclude_export_excluded_all.png') + details.baseName = 'test_exclude_export_excluded_all' + details.page = 0 + self.assertEqual(exporter.exportToImage(rendered_file_path, settings), QgsLayoutExporter.Success) + self.assertFalse(os.path.exists(exporter.generateFileName(details))) + details.page = 1 + self.assertFalse(os.path.exists(exporter.generateFileName(details))) + def testPageFileName(self): l = QgsLayout(QgsProject.instance()) exporter = QgsLayoutExporter(l)