Skip to content

Commit 268acab

Browse files
authoredApr 23, 2017
[FEATURE] New "Save as image" settings dialog (#4390)
* Resolution dpi setting * Extent setting * Scale setting * Draw annotations / decorations setting
1 parent 4a2226a commit 268acab

File tree

9 files changed

+439
-7
lines changed

9 files changed

+439
-7
lines changed
 

‎python/core/qgsmaprenderertask.sip

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ class QgsMapRendererTask : QgsTask
4747
Adds ``annotations`` to be rendered on the map.
4848
%End
4949

50+
void addDecorations( QList< QgsMapDecoration * > decorations );
51+
%Docstring
52+
Adds ``decorations`` to be rendered on the map.
53+
%End
54+
5055
virtual void cancel();
5156

5257

‎src/app/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ SET(QGIS_APP_SRCS
5050
qgsloadstylefromdbdialog.cpp
5151
qgsmapcanvasdockwidget.cpp
5252
qgsmaplayerstyleguiutils.cpp
53+
qgsmapsavedialog.cpp
5354
qgsrulebasedlabelingwidget.cpp
5455
qgssavestyletodbdialog.cpp
5556
qgssnappinglayertreemodel.cpp
@@ -230,6 +231,7 @@ SET (QGIS_APP_MOC_HDRS
230231
qgsloadstylefromdbdialog.h
231232
qgsmapcanvasdockwidget.h
232233
qgsmaplayerstyleguiutils.h
234+
qgsmapsavedialog.h
233235
qgsrulebasedlabelingwidget.h
234236
qgssavestyletodbdialog.h
235237
qgssnappinglayertreemodel.h

‎src/app/qgisapp.cpp

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,9 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
272272
#include "qgsvectorlayerutils.h"
273273
#include "qgshelp.h"
274274
#include "qgsvectorfilewritertask.h"
275+
#include "qgsmapsavedialog.h"
275276
#include "qgsmaprenderertask.h"
277+
#include "qgsmapdecoration.h"
276278
#include "qgsnewnamedialog.h"
277279

278280
#include "qgssublayersdialog.h"
@@ -5781,24 +5783,55 @@ void QgisApp::updateFilterLegend()
57815783

57825784
void QgisApp::saveMapAsImage()
57835785
{
5786+
QList< QgsMapDecoration * > decorations;
5787+
QString activeDecorations;
5788+
Q_FOREACH ( QgsDecorationItem *decoration, mDecorationItems )
5789+
{
5790+
if ( decoration->enabled() )
5791+
{
5792+
decorations << decoration;
5793+
if ( activeDecorations.isEmpty() )
5794+
activeDecorations = decoration->name().toLower();
5795+
else
5796+
activeDecorations += QString( ", %1" ).arg( decoration->name().toLower() );
5797+
}
5798+
}
5799+
5800+
QgsMapSaveDialog dlg( this, mMapCanvas, activeDecorations );
5801+
if ( !dlg.exec() )
5802+
return;
5803+
57845804
QPair< QString, QString> myFileNameAndFilter = QgisGui::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) );
57855805
if ( myFileNameAndFilter.first != QLatin1String( "" ) )
57865806
{
5787-
//TODO: GUI
5788-
int dpi = qt_defaultDpiX();
5789-
QSize size = mMapCanvas->size() * ( dpi / qt_defaultDpiX() );
5807+
QSize size = mMapCanvas->size();
5808+
if ( dlg.extent() != mMapCanvas->extent() )
5809+
{
5810+
size.setWidth( mMapCanvas->size().width() * dlg.extent().width() / mMapCanvas->extent().width() );
5811+
size.setHeight( mMapCanvas->size().height() * dlg.extent().height() / mMapCanvas->extent().height() );
5812+
}
5813+
size *= dlg.dpi() / qt_defaultDpiX();
57905814

57915815
QgsMapSettings ms = QgsMapSettings();
57925816
ms.setDestinationCrs( QgsProject::instance()->crs() );
5793-
ms.setExtent( mMapCanvas->extent() );
5794-
ms.setOutputSize( size );
5795-
ms.setOutputDpi( dpi );
5817+
ms.setExtent( dlg.extent() );
5818+
ms.setOutputSize( dlg.size() );
5819+
ms.setOutputDpi( dlg.dpi() );
57965820
ms.setBackgroundColor( mMapCanvas->canvasColor() );
57975821
ms.setRotation( mMapCanvas->rotation() );
57985822
ms.setLayers( mMapCanvas->layers() );
57995823

58005824
QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, myFileNameAndFilter.first, myFileNameAndFilter.second );
5801-
mapRendererTask->addAnnotations( QgsProject::instance()->annotationManager()->annotations() );
5825+
5826+
if ( dlg.drawAnnotations() )
5827+
{
5828+
mapRendererTask->addAnnotations( QgsProject::instance()->annotationManager()->annotations() );
5829+
}
5830+
5831+
if ( dlg.drawDecorations() )
5832+
{
5833+
mapRendererTask->addDecorations( decorations );
5834+
}
58025835

58035836
connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, this, [ = ]
58045837
{

‎src/app/qgsmapsavedialog.cpp

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/***************************************************************************
2+
qgsmapsavedialog.cpp
3+
-------------------------------------
4+
begin : April 2017
5+
copyright : (C) 2017 by Mathieu Pellerin
6+
email : nirvn dot asia 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 "qgsmapsavedialog.h"
19+
20+
#include "qgis.h"
21+
#include "qgsscalecalculator.h"
22+
#include "qgsdecorationitem.h"
23+
#include "qgsextentgroupbox.h"
24+
#include "qgsmapsettings.h"
25+
26+
#include <QCheckBox>
27+
#include <QSpinBox>
28+
#include <QList>
29+
30+
Q_GUI_EXPORT extern int qt_defaultDpiX();
31+
32+
QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, const QString &activeDecorations )
33+
: QDialog( parent )
34+
{
35+
setupUi( this );
36+
37+
mExtent = mapCanvas->mapSettings().visibleExtent();
38+
mDpi = mapCanvas->mapSettings().outputDpi();
39+
mSize = mapCanvas->mapSettings().outputSize();
40+
41+
mResolutionSpinBox->setValue( qt_defaultDpiX() );
42+
43+
mExtentGroupBox->setOutputCrs( mapCanvas->mapSettings().destinationCrs() );
44+
mExtentGroupBox->setCurrentExtent( mapCanvas->mapSettings().visibleExtent(), mapCanvas->mapSettings().destinationCrs() );
45+
mExtentGroupBox->setOutputExtentFromCurrent();
46+
47+
mScaleWidget->setScale( 1 / mapCanvas->mapSettings().scale() );
48+
mScaleWidget->setMapCanvas( mapCanvas );
49+
mScaleWidget->setShowCurrentScaleButton( true );
50+
51+
mDrawDecorations->setText( QString( "Draw active decorations: %1" ).arg( !activeDecorations.isEmpty() ? activeDecorations : tr( "none" ) ) );
52+
53+
connect( mResolutionSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsMapSaveDialog::updateDpi );
54+
connect( mExtentGroupBox, &QgsExtentGroupBox::extentChanged, this, &QgsMapSaveDialog::updateExtent );
55+
connect( mScaleWidget, &QgsScaleWidget::scaleChanged, this, &QgsMapSaveDialog::updateScale );
56+
57+
updateOutputSize();
58+
}
59+
60+
void QgsMapSaveDialog::updateDpi( int dpi )
61+
{
62+
mSize *= ( double )dpi / mDpi;
63+
mDpi = dpi;
64+
65+
updateOutputSize();
66+
}
67+
68+
void QgsMapSaveDialog::updateExtent( const QgsRectangle &extent )
69+
{
70+
mSize.setWidth( mSize.width() * extent.width() / mExtent.width() );
71+
mSize.setHeight( mSize.height() * extent.height() / mExtent.height() );
72+
mExtent = extent;
73+
74+
updateOutputSize();
75+
}
76+
77+
void QgsMapSaveDialog::updateScale( double scale )
78+
{
79+
QgsScaleCalculator calculator;
80+
calculator.setMapUnits( mExtentGroupBox->currentCrs().mapUnits() );
81+
calculator.setDpi( mDpi );
82+
83+
double oldScale = 1 / ( calculator.calculate( mExtent, mSize.width() ) );
84+
double scaleRatio = oldScale / scale;
85+
mExtent.scale( scaleRatio );
86+
mExtentGroupBox->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() );
87+
}
88+
89+
void QgsMapSaveDialog::updateOutputSize()
90+
{
91+
mOutputSize->setText( QString( "Output size: %1 x %2 pixels" ).arg( mSize.width() ).arg( mSize.height() ) );
92+
}
93+
94+
QgsRectangle QgsMapSaveDialog::extent() const
95+
{
96+
return mExtentGroupBox->outputExtent();
97+
}
98+
99+
int QgsMapSaveDialog::dpi() const
100+
{
101+
return mResolutionSpinBox->value();
102+
}
103+
104+
QSize QgsMapSaveDialog::size() const
105+
{
106+
return mSize;
107+
}
108+
109+
bool QgsMapSaveDialog::drawAnnotations() const
110+
{
111+
return mDrawAnnotations->isChecked();
112+
}
113+
114+
bool QgsMapSaveDialog::drawDecorations() const
115+
{
116+
return mDrawDecorations->isChecked();
117+
}

‎src/app/qgsmapsavedialog.h

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/***************************************************************************
2+
qgsmapsavedialog.h
3+
-------------------------------------
4+
begin : April 2017
5+
copyright : (C) 2017 by Mathieu Pellerin
6+
email : nirvn dot asia 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+
#ifndef QGSMAPSAVEDIALOG_H
19+
#define QGSMAPSAVEDIALOG_H
20+
21+
#include "ui_qgsmapsavedialog.h"
22+
23+
#include "qgisapp.h"
24+
#include "qgsrectangle.h"
25+
#include "qgsmapcanvas.h"
26+
27+
#include <QDialog>
28+
#include <QSize>
29+
30+
/** \ingroup app
31+
* \brief a dialog for saving a map to an image.
32+
* \since QGIS 3.0
33+
*/
34+
class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog
35+
{
36+
Q_OBJECT
37+
38+
public:
39+
40+
/** Constructor for QgsMapSaveDialog
41+
*/
42+
QgsMapSaveDialog( QWidget *parent = nullptr, QgsMapCanvas *mapCanvas = nullptr, const QString &activeDecorations = QString() );
43+
44+
//! returns extent rectangle
45+
QgsRectangle extent() const;
46+
47+
//! returns the numerical value of the dpi spin box
48+
int dpi() const;
49+
50+
//! returns the output size
51+
QSize size() const;
52+
53+
//! returns whether the draw annotations element is checked
54+
bool drawAnnotations() const;
55+
56+
//! returns whether the draw decorations element is checked
57+
bool drawDecorations() const;
58+
59+
private:
60+
61+
void updateDpi( int dpi );
62+
void updateExtent( const QgsRectangle &extent );
63+
void updateScale( double scale );
64+
void updateOutputSize();
65+
66+
QgsRectangle mExtent;
67+
int mDpi;
68+
QSize mSize;
69+
70+
};
71+
72+
#endif // QGSMAPSAVEDIALOG_H

‎src/core/qgsmaprenderertask.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ void QgsMapRendererTask::addAnnotations( QList< QgsAnnotation * > annotations )
4646
}
4747
}
4848

49+
void QgsMapRendererTask::addDecorations( QList< QgsMapDecoration * > decorations )
50+
{
51+
mDecorations = decorations;
52+
}
53+
54+
4955
void QgsMapRendererTask::cancel()
5056
{
5157
mJobMutex.lock();
@@ -97,6 +103,11 @@ bool QgsMapRendererTask::run()
97103
QgsRenderContext context = QgsRenderContext::fromMapSettings( mMapSettings );
98104
context.setPainter( destPainter );
99105

106+
Q_FOREACH ( QgsMapDecoration *decoration, mDecorations )
107+
{
108+
decoration->render( mMapSettings, context );
109+
}
110+
100111
Q_FOREACH ( QgsAnnotation *annotation, mAnnotations )
101112
{
102113
if ( isCanceled() )

‎src/core/qgsmaprenderertask.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "qgsannotation.h"
2424
#include "qgsannotationmanager.h"
2525
#include "qgsmapsettings.h"
26+
#include "qgsmapdecoration.h"
2627
#include "qgstaskmanager.h"
2728
#include "qgsmaprenderercustompainterjob.h"
2829

@@ -67,6 +68,11 @@ class CORE_EXPORT QgsMapRendererTask : public QgsTask
6768
*/
6869
void addAnnotations( QList< QgsAnnotation * > annotations );
6970

71+
/**
72+
* Adds \a decorations to be rendered on the map.
73+
*/
74+
void addDecorations( QList< QgsMapDecoration * > decorations );
75+
7076
void cancel() override;
7177

7278
signals:
@@ -99,6 +105,7 @@ class CORE_EXPORT QgsMapRendererTask : public QgsTask
99105
QString mFileFormat;
100106

101107
QList< QgsAnnotation * > mAnnotations;
108+
QList< QgsMapDecoration * > mDecorations;
102109

103110
int mError = 0;
104111
};

‎src/gui/qgsextentgroupbox.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ QgsExtentGroupBox::QgsExtentGroupBox( QWidget *parent )
2929
mYMinLineEdit->setValidator( new QDoubleValidator( this ) );
3030
mYMaxLineEdit->setValidator( new QDoubleValidator( this ) );
3131

32+
mOriginalExtentButton->setVisible( false );
33+
3234
connect( mCurrentExtentButton, &QAbstractButton::clicked, this, &QgsExtentGroupBox::setOutputExtentFromCurrent );
3335
connect( mOriginalExtentButton, &QAbstractButton::clicked, this, &QgsExtentGroupBox::setOutputExtentFromOriginal );
3436
connect( this, &QGroupBox::clicked, this, &QgsExtentGroupBox::groupBoxClicked );
@@ -39,6 +41,8 @@ void QgsExtentGroupBox::setOriginalExtent( const QgsRectangle &originalExtent, c
3941
{
4042
mOriginalExtent = originalExtent;
4143
mOriginalCrs = originalCrs;
44+
45+
mOriginalExtentButton->setVisible( true );
4246
}
4347

4448

‎src/ui/qgsmapsavedialog.ui

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>QgsMapSaveDialog</class>
4+
<widget class="QDialog" name="QgsMapSaveDialog">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>600</width>
10+
<height>225</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string>Save map as image</string>
15+
</property>
16+
<layout class="QVBoxLayout" name="verticalLayout">
17+
<item>
18+
<layout class="QGridLayout" name="gridLayout">
19+
<item row="5" column="0" colspan="2">>
20+
<widget class="QCheckBox" name="mDrawAnnotations">
21+
<property name="text">
22+
<string>Draw annotations</string>
23+
</property>
24+
<property name="checked">
25+
<bool>true</bool>
26+
</property>
27+
</widget>
28+
</item>
29+
<item row="4" column="0" colspan="2">
30+
<widget class="QCheckBox" name="mDrawDecorations">
31+
<property name="text">
32+
<string>Draw active decorations</string>
33+
</property>
34+
<property name="checked">
35+
<bool>true</bool>
36+
</property>
37+
</widget>
38+
</item>
39+
<item row="3" column="0" colspan="2">
40+
<widget class="QLabel" name="mOutputSize">
41+
<property name="enabled">
42+
<bool>false</bool>
43+
</property>
44+
<property name="alignment">
45+
<set>Qt::AlignCenter</set>
46+
</property>
47+
</widget>
48+
</item>
49+
<item row="2" column="0">
50+
<widget class="QLabel" name="label_2">
51+
<property name="text">
52+
<string>Resolution</string>
53+
</property>
54+
</widget>
55+
</item>
56+
<item row="2" column="1">
57+
<widget class="QgsSpinBox" name="mResolutionSpinBox">
58+
<property name="suffix">
59+
<string> dpi</string>
60+
</property>
61+
<property name="prefix">
62+
<string/>
63+
</property>
64+
<property name="maximum">
65+
<number>3000</number>
66+
</property>
67+
<property name="showClearButton" stdset="0">
68+
<bool>false</bool>
69+
</property>
70+
</widget>
71+
</item>
72+
<item row="1" column="0">
73+
<widget class="QLabel" name="label_1">
74+
<property name="text">
75+
<string>Scale</string>
76+
</property>
77+
</widget>
78+
</item>
79+
<item row="1" column="1">
80+
<widget class="QgsScaleWidget" name="mScaleWidget">
81+
</widget>
82+
</item>
83+
<item row="0" column="0" colspan="2">
84+
<widget class="QgsExtentGroupBox" name="mExtentGroupBox">
85+
<property name="title">
86+
<string>Extent</string>
87+
</property>
88+
</widget>
89+
</item>
90+
</layout>
91+
</item>
92+
<item>
93+
<spacer name="verticalSpacer">
94+
<property name="orientation">
95+
<enum>Qt::Vertical</enum>
96+
</property>
97+
<property name="sizeHint" stdset="0">
98+
<size>
99+
<width>20</width>
100+
<height>40</height>
101+
</size>
102+
</property>
103+
</spacer>
104+
</item>
105+
<item>
106+
<widget class="QDialogButtonBox" name="buttonBox">
107+
<property name="orientation">
108+
<enum>Qt::Horizontal</enum>
109+
</property>
110+
<property name="standardButtons">
111+
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
112+
</property>
113+
</widget>
114+
</item>
115+
</layout>
116+
</widget>
117+
<customwidgets>
118+
<customwidget>
119+
<class>QgsCollapsibleGroupBox</class>
120+
<extends>QGroupBox</extends>
121+
<header>qgscollapsiblegroupbox.h</header>
122+
<container>1</container>
123+
</customwidget>
124+
<customwidget>
125+
<class>QgsExtentGroupBox</class>
126+
<extends>QgsCollapsibleGroupBox</extends>
127+
<header>qgsextentgroupbox.h</header>
128+
<container>1</container>
129+
</customwidget>
130+
<customwidget>
131+
<class>QgsSpinBox</class>
132+
<extends>QSpinBox</extends>
133+
<header>qgsspinbox.h</header>
134+
</customwidget>
135+
<customwidget>
136+
<class>QgsScaleWidget</class>
137+
<extends>QWidget</extends>
138+
<header>qgsscalewidget.h</header>
139+
</customwidget>
140+
</customwidgets>
141+
<tabstops>
142+
<tabstop>mExtentGroupBox</tabstop>
143+
<tabstop>mDrawAnnotations</tabstop>
144+
<tabstop>mDrawDecorations</tabstop>
145+
</tabstops>
146+
<resources/>
147+
<connections>
148+
<connection>
149+
<sender>buttonBox</sender>
150+
<signal>accepted()</signal>
151+
<receiver>QgsMapSaveDialog</receiver>
152+
<slot>accept()</slot>
153+
<hints>
154+
<hint type="sourcelabel">
155+
<x>248</x>
156+
<y>254</y>
157+
</hint>
158+
<hint type="destinationlabel">
159+
<x>157</x>
160+
<y>274</y>
161+
</hint>
162+
</hints>
163+
</connection>
164+
<connection>
165+
<sender>buttonBox</sender>
166+
<signal>rejected()</signal>
167+
<receiver>QgsMapSaveDialog</receiver>
168+
<slot>reject()</slot>
169+
<hints>
170+
<hint type="sourcelabel">
171+
<x>316</x>
172+
<y>260</y>
173+
</hint>
174+
<hint type="destinationlabel">
175+
<x>286</x>
176+
<y>274</y>
177+
</hint>
178+
</hints>
179+
</connection>
180+
</connections>
181+
</ui>

0 commit comments

Comments
 (0)
Please sign in to comment.