[FEATURE] New "Save as image" settings dialog (#4390)

* Resolution dpi setting
* Extent setting
* Scale setting
* Draw annotations / decorations setting
This commit is contained in:
Mathieu Pellerin 2017-04-23 13:02:42 +07:00 committed by GitHub
parent 4a2226af31
commit 268acab5d6
9 changed files with 439 additions and 7 deletions

View File

@ -47,6 +47,11 @@ class QgsMapRendererTask : QgsTask
Adds ``annotations`` to be rendered on the map.
%End
void addDecorations( QList< QgsMapDecoration * > decorations );
%Docstring
Adds ``decorations`` to be rendered on the map.
%End
virtual void cancel();

View File

@ -50,6 +50,7 @@ SET(QGIS_APP_SRCS
qgsloadstylefromdbdialog.cpp
qgsmapcanvasdockwidget.cpp
qgsmaplayerstyleguiutils.cpp
qgsmapsavedialog.cpp
qgsrulebasedlabelingwidget.cpp
qgssavestyletodbdialog.cpp
qgssnappinglayertreemodel.cpp
@ -230,6 +231,7 @@ SET (QGIS_APP_MOC_HDRS
qgsloadstylefromdbdialog.h
qgsmapcanvasdockwidget.h
qgsmaplayerstyleguiutils.h
qgsmapsavedialog.h
qgsrulebasedlabelingwidget.h
qgssavestyletodbdialog.h
qgssnappinglayertreemodel.h

View File

@ -272,7 +272,9 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgsvectorlayerutils.h"
#include "qgshelp.h"
#include "qgsvectorfilewritertask.h"
#include "qgsmapsavedialog.h"
#include "qgsmaprenderertask.h"
#include "qgsmapdecoration.h"
#include "qgsnewnamedialog.h"
#include "qgssublayersdialog.h"
@ -5781,24 +5783,55 @@ void QgisApp::updateFilterLegend()
void QgisApp::saveMapAsImage()
{
QList< QgsMapDecoration * > decorations;
QString activeDecorations;
Q_FOREACH ( QgsDecorationItem *decoration, mDecorationItems )
{
if ( decoration->enabled() )
{
decorations << decoration;
if ( activeDecorations.isEmpty() )
activeDecorations = decoration->name().toLower();
else
activeDecorations += QString( ", %1" ).arg( decoration->name().toLower() );
}
}
QgsMapSaveDialog dlg( this, mMapCanvas, activeDecorations );
if ( !dlg.exec() )
return;
QPair< QString, QString> myFileNameAndFilter = QgisGui::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) );
if ( myFileNameAndFilter.first != QLatin1String( "" ) )
{
//TODO: GUI
int dpi = qt_defaultDpiX();
QSize size = mMapCanvas->size() * ( dpi / qt_defaultDpiX() );
QSize size = mMapCanvas->size();
if ( dlg.extent() != mMapCanvas->extent() )
{
size.setWidth( mMapCanvas->size().width() * dlg.extent().width() / mMapCanvas->extent().width() );
size.setHeight( mMapCanvas->size().height() * dlg.extent().height() / mMapCanvas->extent().height() );
}
size *= dlg.dpi() / qt_defaultDpiX();
QgsMapSettings ms = QgsMapSettings();
ms.setDestinationCrs( QgsProject::instance()->crs() );
ms.setExtent( mMapCanvas->extent() );
ms.setOutputSize( size );
ms.setOutputDpi( dpi );
ms.setExtent( dlg.extent() );
ms.setOutputSize( dlg.size() );
ms.setOutputDpi( dlg.dpi() );
ms.setBackgroundColor( mMapCanvas->canvasColor() );
ms.setRotation( mMapCanvas->rotation() );
ms.setLayers( mMapCanvas->layers() );
QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, myFileNameAndFilter.first, myFileNameAndFilter.second );
mapRendererTask->addAnnotations( QgsProject::instance()->annotationManager()->annotations() );
if ( dlg.drawAnnotations() )
{
mapRendererTask->addAnnotations( QgsProject::instance()->annotationManager()->annotations() );
}
if ( dlg.drawDecorations() )
{
mapRendererTask->addDecorations( decorations );
}
connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, this, [ = ]
{

View File

@ -0,0 +1,117 @@
/***************************************************************************
qgsmapsavedialog.cpp
-------------------------------------
begin : April 2017
copyright : (C) 2017 by Mathieu Pellerin
email : nirvn dot asia at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsmapsavedialog.h"
#include "qgis.h"
#include "qgsscalecalculator.h"
#include "qgsdecorationitem.h"
#include "qgsextentgroupbox.h"
#include "qgsmapsettings.h"
#include <QCheckBox>
#include <QSpinBox>
#include <QList>
Q_GUI_EXPORT extern int qt_defaultDpiX();
QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, const QString &activeDecorations )
: QDialog( parent )
{
setupUi( this );
mExtent = mapCanvas->mapSettings().visibleExtent();
mDpi = mapCanvas->mapSettings().outputDpi();
mSize = mapCanvas->mapSettings().outputSize();
mResolutionSpinBox->setValue( qt_defaultDpiX() );
mExtentGroupBox->setOutputCrs( mapCanvas->mapSettings().destinationCrs() );
mExtentGroupBox->setCurrentExtent( mapCanvas->mapSettings().visibleExtent(), mapCanvas->mapSettings().destinationCrs() );
mExtentGroupBox->setOutputExtentFromCurrent();
mScaleWidget->setScale( 1 / mapCanvas->mapSettings().scale() );
mScaleWidget->setMapCanvas( mapCanvas );
mScaleWidget->setShowCurrentScaleButton( true );
mDrawDecorations->setText( QString( "Draw active decorations: %1" ).arg( !activeDecorations.isEmpty() ? activeDecorations : tr( "none" ) ) );
connect( mResolutionSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsMapSaveDialog::updateDpi );
connect( mExtentGroupBox, &QgsExtentGroupBox::extentChanged, this, &QgsMapSaveDialog::updateExtent );
connect( mScaleWidget, &QgsScaleWidget::scaleChanged, this, &QgsMapSaveDialog::updateScale );
updateOutputSize();
}
void QgsMapSaveDialog::updateDpi( int dpi )
{
mSize *= ( double )dpi / mDpi;
mDpi = dpi;
updateOutputSize();
}
void QgsMapSaveDialog::updateExtent( const QgsRectangle &extent )
{
mSize.setWidth( mSize.width() * extent.width() / mExtent.width() );
mSize.setHeight( mSize.height() * extent.height() / mExtent.height() );
mExtent = extent;
updateOutputSize();
}
void QgsMapSaveDialog::updateScale( double scale )
{
QgsScaleCalculator calculator;
calculator.setMapUnits( mExtentGroupBox->currentCrs().mapUnits() );
calculator.setDpi( mDpi );
double oldScale = 1 / ( calculator.calculate( mExtent, mSize.width() ) );
double scaleRatio = oldScale / scale;
mExtent.scale( scaleRatio );
mExtentGroupBox->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() );
}
void QgsMapSaveDialog::updateOutputSize()
{
mOutputSize->setText( QString( "Output size: %1 x %2 pixels" ).arg( mSize.width() ).arg( mSize.height() ) );
}
QgsRectangle QgsMapSaveDialog::extent() const
{
return mExtentGroupBox->outputExtent();
}
int QgsMapSaveDialog::dpi() const
{
return mResolutionSpinBox->value();
}
QSize QgsMapSaveDialog::size() const
{
return mSize;
}
bool QgsMapSaveDialog::drawAnnotations() const
{
return mDrawAnnotations->isChecked();
}
bool QgsMapSaveDialog::drawDecorations() const
{
return mDrawDecorations->isChecked();
}

View File

@ -0,0 +1,72 @@
/***************************************************************************
qgsmapsavedialog.h
-------------------------------------
begin : April 2017
copyright : (C) 2017 by Mathieu Pellerin
email : nirvn dot asia at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSMAPSAVEDIALOG_H
#define QGSMAPSAVEDIALOG_H
#include "ui_qgsmapsavedialog.h"
#include "qgisapp.h"
#include "qgsrectangle.h"
#include "qgsmapcanvas.h"
#include <QDialog>
#include <QSize>
/** \ingroup app
* \brief a dialog for saving a map to an image.
* \since QGIS 3.0
*/
class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog
{
Q_OBJECT
public:
/** Constructor for QgsMapSaveDialog
*/
QgsMapSaveDialog( QWidget *parent = nullptr, QgsMapCanvas *mapCanvas = nullptr, const QString &activeDecorations = QString() );
//! returns extent rectangle
QgsRectangle extent() const;
//! returns the numerical value of the dpi spin box
int dpi() const;
//! returns the output size
QSize size() const;
//! returns whether the draw annotations element is checked
bool drawAnnotations() const;
//! returns whether the draw decorations element is checked
bool drawDecorations() const;
private:
void updateDpi( int dpi );
void updateExtent( const QgsRectangle &extent );
void updateScale( double scale );
void updateOutputSize();
QgsRectangle mExtent;
int mDpi;
QSize mSize;
};
#endif // QGSMAPSAVEDIALOG_H

View File

@ -46,6 +46,12 @@ void QgsMapRendererTask::addAnnotations( QList< QgsAnnotation * > annotations )
}
}
void QgsMapRendererTask::addDecorations( QList< QgsMapDecoration * > decorations )
{
mDecorations = decorations;
}
void QgsMapRendererTask::cancel()
{
mJobMutex.lock();
@ -97,6 +103,11 @@ bool QgsMapRendererTask::run()
QgsRenderContext context = QgsRenderContext::fromMapSettings( mMapSettings );
context.setPainter( destPainter );
Q_FOREACH ( QgsMapDecoration *decoration, mDecorations )
{
decoration->render( mMapSettings, context );
}
Q_FOREACH ( QgsAnnotation *annotation, mAnnotations )
{
if ( isCanceled() )

View File

@ -23,6 +23,7 @@
#include "qgsannotation.h"
#include "qgsannotationmanager.h"
#include "qgsmapsettings.h"
#include "qgsmapdecoration.h"
#include "qgstaskmanager.h"
#include "qgsmaprenderercustompainterjob.h"
@ -67,6 +68,11 @@ class CORE_EXPORT QgsMapRendererTask : public QgsTask
*/
void addAnnotations( QList< QgsAnnotation * > annotations );
/**
* Adds \a decorations to be rendered on the map.
*/
void addDecorations( QList< QgsMapDecoration * > decorations );
void cancel() override;
signals:
@ -99,6 +105,7 @@ class CORE_EXPORT QgsMapRendererTask : public QgsTask
QString mFileFormat;
QList< QgsAnnotation * > mAnnotations;
QList< QgsMapDecoration * > mDecorations;
int mError = 0;
};

View File

@ -29,6 +29,8 @@ QgsExtentGroupBox::QgsExtentGroupBox( QWidget *parent )
mYMinLineEdit->setValidator( new QDoubleValidator( this ) );
mYMaxLineEdit->setValidator( new QDoubleValidator( this ) );
mOriginalExtentButton->setVisible( false );
connect( mCurrentExtentButton, &QAbstractButton::clicked, this, &QgsExtentGroupBox::setOutputExtentFromCurrent );
connect( mOriginalExtentButton, &QAbstractButton::clicked, this, &QgsExtentGroupBox::setOutputExtentFromOriginal );
connect( this, &QGroupBox::clicked, this, &QgsExtentGroupBox::groupBoxClicked );
@ -39,6 +41,8 @@ void QgsExtentGroupBox::setOriginalExtent( const QgsRectangle &originalExtent, c
{
mOriginalExtent = originalExtent;
mOriginalCrs = originalCrs;
mOriginalExtentButton->setVisible( true );
}

181
src/ui/qgsmapsavedialog.ui Normal file
View File

@ -0,0 +1,181 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsMapSaveDialog</class>
<widget class="QDialog" name="QgsMapSaveDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>225</height>
</rect>
</property>
<property name="windowTitle">
<string>Save map as image</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="5" column="0" colspan="2">>
<widget class="QCheckBox" name="mDrawAnnotations">
<property name="text">
<string>Draw annotations</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="mDrawDecorations">
<property name="text">
<string>Draw active decorations</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="mOutputSize">
<property name="enabled">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Resolution</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QgsSpinBox" name="mResolutionSpinBox">
<property name="suffix">
<string> dpi</string>
</property>
<property name="prefix">
<string/>
</property>
<property name="maximum">
<number>3000</number>
</property>
<property name="showClearButton" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_1">
<property name="text">
<string>Scale</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QgsScaleWidget" name="mScaleWidget">
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QgsExtentGroupBox" name="mExtentGroupBox">
<property name="title">
<string>Extent</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsCollapsibleGroupBox</class>
<extends>QGroupBox</extends>
<header>qgscollapsiblegroupbox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsExtentGroupBox</class>
<extends>QgsCollapsibleGroupBox</extends>
<header>qgsextentgroupbox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsSpinBox</class>
<extends>QSpinBox</extends>
<header>qgsspinbox.h</header>
</customwidget>
<customwidget>
<class>QgsScaleWidget</class>
<extends>QWidget</extends>
<header>qgsscalewidget.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mExtentGroupBox</tabstop>
<tabstop>mDrawAnnotations</tabstop>
<tabstop>mDrawDecorations</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>QgsMapSaveDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>QgsMapSaveDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>