Skip to content

Commit 953d2c4

Browse files
committed
[layouts] Resurrect action for exporting to raster images
...this time, without all the useful code locked away in app!
1 parent 113664f commit 953d2c4

8 files changed

Lines changed: 804 additions & 0 deletions

src/app/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ SET(QGIS_APP_SRCS
185185
layout/qgslayoutdesignerdialog.cpp
186186
layout/qgslayoutguidewidget.cpp
187187
layout/qgslayouthtmlwidget.cpp
188+
layout/qgslayoutimageexportoptionsdialog.cpp
188189
layout/qgslayoutitemslistview.cpp
189190
layout/qgslayoutappmenuprovider.cpp
190191
layout/qgslayoutlabelwidget.cpp
@@ -403,6 +404,7 @@ SET (QGIS_APP_MOC_HDRS
403404
layout/qgslayoutdesignerdialog.h
404405
layout/qgslayoutguidewidget.h
405406
layout/qgslayouthtmlwidget.h
407+
layout/qgslayoutimageexportoptionsdialog.h
406408
layout/qgslayoutitemslistview.h
407409
layout/qgslayoutlabelwidget.h
408410
layout/qgslayoutlegendwidget.h

src/app/layout/qgslayoutdesignerdialog.cpp

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
#include "qgslayoutviewtoolselect.h"
3434
#include "qgslayoutviewtooleditnodes.h"
3535
#include "qgslayoutitemwidget.h"
36+
#include "qgslayoutimageexportoptionsdialog.h"
37+
#include "qgslayoutitemmap.h"
38+
#include "qgsmessageviewer.h"
3639
#include "qgsgui.h"
3740
#include "qgslayoutitemguiregistry.h"
3841
#include "qgslayoutpropertieswidget.h"
@@ -168,6 +171,8 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
168171
connect( mActionLayoutManager, &QAction::triggered, this, &QgsLayoutDesignerDialog::showManager );
169172
connect( mActionRemoveLayout, &QAction::triggered, this, &QgsLayoutDesignerDialog::deleteLayout );
170173

174+
connect( mActionExportAsImage, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportToRaster );
175+
171176
connect( mActionShowGrid, &QAction::triggered, this, &QgsLayoutDesignerDialog::showGrid );
172177
connect( mActionSnapGrid, &QAction::triggered, this, &QgsLayoutDesignerDialog::snapToGrid );
173178

@@ -1410,6 +1415,123 @@ void QgsLayoutDesignerDialog::deleteLayout()
14101415
close();
14111416
}
14121417

1418+
void QgsLayoutDesignerDialog::exportToRaster()
1419+
{
1420+
if ( containsWmsLayers() )
1421+
showWmsPrintingWarning();
1422+
1423+
// Image size
1424+
double oneInchInLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) );
1425+
QSizeF maxPageSize = mLayout->pageCollection()->maximumPageSize();
1426+
bool hasUniformPageSizes = mLayout->pageCollection()->hasUniformPageSizes();
1427+
int width = ( int )( mLayout->context().dpi() * maxPageSize.width() / oneInchInLayoutUnits );
1428+
int height = ( int )( mLayout->context().dpi() * maxPageSize.height() / oneInchInLayoutUnits );
1429+
double dpi = mLayout->context().dpi();
1430+
1431+
int memuse = width * height * 3 / 1000000; // pixmap + image
1432+
QgsDebugMsg( QString( "Image %1x%2" ).arg( width ).arg( height ) );
1433+
QgsDebugMsg( QString( "memuse = %1" ).arg( memuse ) );
1434+
1435+
if ( memuse > 400 ) // about 4500x4500
1436+
{
1437+
int answer = QMessageBox::warning( nullptr, tr( "Export layout" ),
1438+
tr( "To create an image of %1x%2 requires about %3 MB of memory. Proceed?" )
1439+
.arg( width ).arg( height ).arg( memuse ),
1440+
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok );
1441+
1442+
raise();
1443+
if ( answer == QMessageBox::Cancel )
1444+
return;
1445+
}
1446+
1447+
//get some defaults from the composition
1448+
bool cropToContents = mLayout->customProperty( QStringLiteral( "imageCropToContents" ), false ).toBool();
1449+
int marginTop = mLayout->customProperty( QStringLiteral( "imageCropMarginTop" ), 0 ).toInt();
1450+
int marginRight = mLayout->customProperty( QStringLiteral( "imageCropMarginRight" ), 0 ).toInt();
1451+
int marginBottom = mLayout->customProperty( QStringLiteral( "imageCropMarginBottom" ), 0 ).toInt();
1452+
int marginLeft = mLayout->customProperty( QStringLiteral( "imageCropMarginLeft" ), 0 ).toInt();
1453+
1454+
QgsLayoutImageExportOptionsDialog imageDlg( this );
1455+
imageDlg.setImageSize( maxPageSize );
1456+
imageDlg.setResolution( dpi );
1457+
imageDlg.setCropToContents( cropToContents );
1458+
imageDlg.setCropMargins( marginTop, marginRight, marginBottom, marginLeft );
1459+
1460+
#if 0 //TODO
1461+
QgsAtlasComposition *atlasMap = &mComposition->atlasComposition();
1462+
#endif
1463+
1464+
QString outputFileName;
1465+
#if 0 //TODO
1466+
if ( atlasMap->enabled() && mComposition->atlasMode() == QgsComposition::PreviewAtlas )
1467+
{
1468+
QString lastUsedDir = settings.value( QStringLiteral( "UI/lastSaveAsImageDir" ), QDir::homePath() ).toString();
1469+
outputFileName = QDir( lastUsedDir ).filePath( atlasMap->currentFilename() );
1470+
}
1471+
#endif
1472+
1473+
#ifdef Q_OS_MAC
1474+
mQgis->activateWindow();
1475+
this->raise();
1476+
#endif
1477+
QPair<QString, QString> fileNExt = QgsGuiUtils::getSaveAsImageName( this, tr( "Save layout as" ), outputFileName );
1478+
this->activateWindow();
1479+
1480+
if ( fileNExt.first.isEmpty() )
1481+
{
1482+
return;
1483+
}
1484+
1485+
if ( !imageDlg.exec() )
1486+
return;
1487+
1488+
cropToContents = imageDlg.cropToContents();
1489+
imageDlg.getCropMargins( marginTop, marginRight, marginBottom, marginLeft );
1490+
mLayout->setCustomProperty( QStringLiteral( "imageCropToContents" ), cropToContents );
1491+
mLayout->setCustomProperty( QStringLiteral( "imageCropMarginTop" ), marginTop );
1492+
mLayout->setCustomProperty( QStringLiteral( "imageCropMarginRight" ), marginRight );
1493+
mLayout->setCustomProperty( QStringLiteral( "imageCropMarginBottom" ), marginBottom );
1494+
mLayout->setCustomProperty( QStringLiteral( "imageCropMarginLeft" ), marginLeft );
1495+
1496+
mView->setPaintingEnabled( false );
1497+
1498+
QgsLayoutExporter exporter( mLayout );
1499+
1500+
QgsLayoutExporter::ImageExportSettings settings;
1501+
settings.cropToContents = cropToContents;
1502+
settings.cropMargins = QgsMargins( marginLeft, marginTop, marginRight, marginBottom );
1503+
settings.dpi = imageDlg.resolution();
1504+
if ( hasUniformPageSizes )
1505+
{
1506+
settings.imageSize = QSize( imageDlg.imageWidth(), imageDlg.imageHeight() );
1507+
}
1508+
settings.generateWorldFile = mLayout->customProperty( QStringLiteral( "exportWorldFile" ), false ).toBool();
1509+
1510+
switch ( exporter.exportToImage( fileNExt.first, settings ) )
1511+
{
1512+
case QgsLayoutExporter::Success:
1513+
break;
1514+
1515+
case QgsLayoutExporter::FileError:
1516+
QMessageBox::warning( this, tr( "Image Export Error" ),
1517+
QString( tr( "Cannot write to %1.\n\nThis file may be open in another application." ) ).arg( exporter.errorFile() ),
1518+
QMessageBox::Ok,
1519+
QMessageBox::Ok );
1520+
break;
1521+
1522+
case QgsLayoutExporter::MemoryError:
1523+
QMessageBox::warning( nullptr, tr( "Memory Allocation Error" ),
1524+
tr( "Trying to create image %1 (%2×%3 @ %4dpi ) "
1525+
"resulted in a memory overflow.\n\n"
1526+
"Please try a lower resolution or a smaller paper size." )
1527+
.arg( exporter.errorFile() ).arg( imageDlg.imageWidth() ).arg( imageDlg.imageHeight() ).arg( settings.dpi ),
1528+
QMessageBox::Ok, QMessageBox::Ok );
1529+
break;
1530+
1531+
}
1532+
mView->setPaintingEnabled( true );
1533+
}
1534+
14131535
void QgsLayoutDesignerDialog::paste()
14141536
{
14151537
QPointF pt = mView->mapFromGlobal( QCursor::pos() );
@@ -1525,6 +1647,36 @@ void QgsLayoutDesignerDialog::initializeRegistry()
15251647

15261648
}
15271649

1650+
bool QgsLayoutDesignerDialog::containsWmsLayers() const
1651+
{
1652+
QList< QgsLayoutItemMap *> maps;
1653+
mLayout->layoutItems( maps );
1654+
1655+
for ( QgsLayoutItemMap *map : qgis::as_const( maps ) )
1656+
{
1657+
if ( map->containsWmsLayer() )
1658+
return true;
1659+
}
1660+
return false;
1661+
}
1662+
1663+
void QgsLayoutDesignerDialog::showWmsPrintingWarning()
1664+
{
1665+
QgsSettings settings;
1666+
bool displayWMSWarning = settings.value( QStringLiteral( "/UI/displayComposerWMSWarning" ), true ).toBool();
1667+
if ( displayWMSWarning )
1668+
{
1669+
QgsMessageViewer *m = new QgsMessageViewer( this );
1670+
m->setWindowTitle( tr( "Project Contains WMS Layers" ) );
1671+
m->setMessage( tr( "Some WMS servers (e.g. UMN mapserver) have a limit for the WIDTH and HEIGHT parameter. Printing layers from such servers may exceed this limit. If this is the case, the WMS layer will not be printed" ), QgsMessageOutput::MessageText );
1672+
m->setCheckBoxText( tr( "Don't show this message again" ) );
1673+
m->setCheckBoxState( Qt::Unchecked );
1674+
m->setCheckBoxVisible( true );
1675+
m->setCheckBoxQgsSettingsLabel( QStringLiteral( "/UI/displayComposerWMSWarning" ) );
1676+
m->exec(); //deleted on close
1677+
}
1678+
}
1679+
15281680
void QgsLayoutDesignerDialog::selectItems( const QList<QgsLayoutItem *> items )
15291681
{
15301682
for ( QGraphicsItem *item : items )

src/app/layout/qgslayoutdesignerdialog.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
274274
void showManager();
275275
void renameLayout();
276276
void deleteLayout();
277+
void exportToRaster();
277278

278279
private:
279280

@@ -360,6 +361,10 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
360361

361362
void initializeRegistry();
362363

364+
bool containsWmsLayers() const;
365+
366+
//! Displays a warning because of possible min/max size in WMS
367+
void showWmsPrintingWarning();
363368
};
364369

365370
#endif // QGSLAYOUTDESIGNERDIALOG_H
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/***************************************************************************
2+
qgslayoutimageexportoptionsdialog.cpp
3+
-------------------------------------
4+
begin : December 2017
5+
copyright : (C) 2017 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgslayoutimageexportoptionsdialog.h"
19+
#include "qgis.h"
20+
#include "qgssettings.h"
21+
#include "qgsgui.h"
22+
23+
#include <QCheckBox>
24+
#include <QPushButton>
25+
26+
QgsLayoutImageExportOptionsDialog::QgsLayoutImageExportOptionsDialog( QWidget *parent, Qt::WindowFlags flags )
27+
: QDialog( parent, flags )
28+
{
29+
setupUi( this );
30+
connect( mWidthSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutImageExportOptionsDialog::mWidthSpinBox_valueChanged );
31+
connect( mHeightSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutImageExportOptionsDialog::mHeightSpinBox_valueChanged );
32+
connect( mResolutionSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutImageExportOptionsDialog::mResolutionSpinBox_valueChanged );
33+
34+
connect( mClipToContentGroupBox, &QGroupBox::toggled, this, &QgsLayoutImageExportOptionsDialog::clipToContentsToggled );
35+
36+
QgsGui::instance()->enableAutoGeometryRestore( this );
37+
}
38+
39+
void QgsLayoutImageExportOptionsDialog::setResolution( double resolution )
40+
{
41+
mResolutionSpinBox->setValue( resolution );
42+
43+
if ( mImageSize.isValid() )
44+
{
45+
mWidthSpinBox->blockSignals( true );
46+
mHeightSpinBox->blockSignals( true );
47+
if ( mClipToContentGroupBox->isChecked() )
48+
{
49+
mWidthSpinBox->setValue( 0 );
50+
mHeightSpinBox->setValue( 0 );
51+
}
52+
else
53+
{
54+
mWidthSpinBox->setValue( mImageSize.width() * resolution / 25.4 );
55+
mHeightSpinBox->setValue( mImageSize.height() * resolution / 25.4 );
56+
}
57+
mWidthSpinBox->blockSignals( false );
58+
mHeightSpinBox->blockSignals( false );
59+
}
60+
}
61+
62+
double QgsLayoutImageExportOptionsDialog::resolution() const
63+
{
64+
return mResolutionSpinBox->value();
65+
}
66+
67+
void QgsLayoutImageExportOptionsDialog::setImageSize( QSizeF size )
68+
{
69+
mImageSize = size;
70+
mWidthSpinBox->blockSignals( true );
71+
mHeightSpinBox->blockSignals( true );
72+
mWidthSpinBox->setValue( size.width() * mResolutionSpinBox->value() / 25.4 );
73+
mHeightSpinBox->setValue( size.height() * mResolutionSpinBox->value() / 25.4 );
74+
mWidthSpinBox->blockSignals( false );
75+
mHeightSpinBox->blockSignals( false );
76+
}
77+
78+
int QgsLayoutImageExportOptionsDialog::imageWidth() const
79+
{
80+
return mWidthSpinBox->value();
81+
}
82+
83+
int QgsLayoutImageExportOptionsDialog::imageHeight() const
84+
{
85+
return mHeightSpinBox->value();
86+
}
87+
88+
void QgsLayoutImageExportOptionsDialog::setCropToContents( bool crop )
89+
{
90+
mClipToContentGroupBox->setChecked( crop );
91+
}
92+
93+
bool QgsLayoutImageExportOptionsDialog::cropToContents() const
94+
{
95+
return mClipToContentGroupBox->isChecked();
96+
}
97+
98+
void QgsLayoutImageExportOptionsDialog::getCropMargins( int &topMargin, int &rightMargin, int &bottomMargin, int &leftMargin ) const
99+
{
100+
topMargin = mTopMarginSpinBox->value();
101+
rightMargin = mRightMarginSpinBox->value();
102+
bottomMargin = mBottomMarginSpinBox->value();
103+
leftMargin = mLeftMarginSpinBox->value();
104+
}
105+
106+
void QgsLayoutImageExportOptionsDialog::setCropMargins( int topMargin, int rightMargin, int bottomMargin, int leftMargin )
107+
{
108+
mTopMarginSpinBox->setValue( topMargin );
109+
mRightMarginSpinBox->setValue( rightMargin );
110+
mBottomMarginSpinBox->setValue( bottomMargin );
111+
mLeftMarginSpinBox->setValue( leftMargin );
112+
}
113+
114+
void QgsLayoutImageExportOptionsDialog::mWidthSpinBox_valueChanged( int value )
115+
{
116+
mHeightSpinBox->blockSignals( true );
117+
mResolutionSpinBox->blockSignals( true );
118+
mHeightSpinBox->setValue( mImageSize.height() * value / mImageSize.width() );
119+
mResolutionSpinBox->setValue( value * 25.4 / mImageSize.width() );
120+
mHeightSpinBox->blockSignals( false );
121+
mResolutionSpinBox->blockSignals( false );
122+
}
123+
124+
void QgsLayoutImageExportOptionsDialog::mHeightSpinBox_valueChanged( int value )
125+
{
126+
mWidthSpinBox->blockSignals( true );
127+
mResolutionSpinBox->blockSignals( true );
128+
mWidthSpinBox->setValue( mImageSize.width() * value / mImageSize.height() );
129+
mResolutionSpinBox->setValue( value * 25.4 / mImageSize.height() );
130+
mWidthSpinBox->blockSignals( false );
131+
mResolutionSpinBox->blockSignals( false );
132+
}
133+
134+
void QgsLayoutImageExportOptionsDialog::mResolutionSpinBox_valueChanged( int value )
135+
{
136+
mWidthSpinBox->blockSignals( true );
137+
mHeightSpinBox->blockSignals( true );
138+
if ( mClipToContentGroupBox->isChecked() )
139+
{
140+
mWidthSpinBox->setValue( 0 );
141+
mHeightSpinBox->setValue( 0 );
142+
}
143+
else
144+
{
145+
mWidthSpinBox->setValue( mImageSize.width() * value / 25.4 );
146+
mHeightSpinBox->setValue( mImageSize.height() * value / 25.4 );
147+
}
148+
mWidthSpinBox->blockSignals( false );
149+
mHeightSpinBox->blockSignals( false );
150+
}
151+
152+
void QgsLayoutImageExportOptionsDialog::clipToContentsToggled( bool state )
153+
{
154+
mWidthSpinBox->setEnabled( !state );
155+
mHeightSpinBox->setEnabled( !state );
156+
157+
if ( state )
158+
{
159+
whileBlocking( mWidthSpinBox )->setValue( 0 );
160+
whileBlocking( mHeightSpinBox )->setValue( 0 );
161+
}
162+
else
163+
{
164+
whileBlocking( mWidthSpinBox )->setValue( mImageSize.width() * mResolutionSpinBox->value() / 25.4 );
165+
whileBlocking( mHeightSpinBox )->setValue( mImageSize.height() * mResolutionSpinBox->value() / 25.4 );
166+
}
167+
}

0 commit comments

Comments
 (0)