Non-blocking save as image/PDF dialogs (#4874)

This commit is contained in:
Mathieu Pellerin 2017-07-18 10:47:06 +07:00 committed by GitHub
parent d70f53c405
commit 3037f22482
6 changed files with 183 additions and 173 deletions

View File

@ -31,7 +31,8 @@ class QgsMapRendererTask : QgsTask
QgsMapRendererTask( const QgsMapSettings &ms, QgsMapRendererTask( const QgsMapSettings &ms,
const QString &fileName, const QString &fileName,
const QString &fileFormat = QString( "PNG" ) ); const QString &fileFormat = QString( "PNG" ),
const bool forceRaster = false );
%Docstring %Docstring
Constructor for QgsMapRendererTask to render a map to an image file. Constructor for QgsMapRendererTask to render a map to an image file.
%End %End

View File

@ -5740,173 +5740,34 @@ void QgisApp::updateFilterLegend()
void QgisApp::saveMapAsImage() void QgisApp::saveMapAsImage()
{ {
QList< QgsMapDecoration * > decorations; QList< QgsDecorationItem * > decorations;
QString activeDecorations;
Q_FOREACH ( QgsDecorationItem *decoration, mDecorationItems ) Q_FOREACH ( QgsDecorationItem *decoration, mDecorationItems )
{ {
if ( decoration->enabled() ) if ( decoration->enabled() )
{ {
decorations << decoration; decorations << decoration;
if ( activeDecorations.isEmpty() )
activeDecorations = decoration->name().toLower();
else
activeDecorations += QString( ", %1" ).arg( decoration->name().toLower() );
} }
} }
QgsMapSaveDialog dlg( this, mMapCanvas, activeDecorations ); QgsMapSaveDialog *dlg = new QgsMapSaveDialog( this, mMapCanvas, decorations, QgsProject::instance()->annotationManager()->annotations() );
if ( !dlg.exec() ) dlg->setAttribute( Qt::WA_DeleteOnClose );
return; dlg->show();
QPair< QString, QString> fileNameAndFilter = QgsGuiUtils::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) );
if ( fileNameAndFilter.first != QLatin1String( "" ) )
{
QgsMapSettings ms = QgsMapSettings();
dlg.applyMapSettings( ms );
QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, fileNameAndFilter.first, fileNameAndFilter.second );
if ( dlg.drawAnnotations() )
{
mapRendererTask->addAnnotations( QgsProject::instance()->annotationManager()->annotations() );
}
if ( dlg.drawDecorations() )
{
mapRendererTask->addDecorations( decorations );
}
mapRendererTask->setSaveWorldFile( dlg.saveWorldFile() );
connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, this, [ = ]
{
messageBar()->pushSuccess( tr( "Save as image" ), tr( "Successfully saved map to image" ) );
} );
connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, this, [ = ]( int error )
{
switch ( error )
{
case QgsMapRendererTask::ImageAllocationFail:
{
messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not allocate required memory for image" ) );
break;
}
case QgsMapRendererTask::ImageSaveFail:
{
messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not save the image to file" ) );
break;
}
}
} );
QgsApplication::taskManager()->addTask( mapRendererTask );
}
} // saveMapAsImage } // saveMapAsImage
void QgisApp::saveMapAsPdf() void QgisApp::saveMapAsPdf()
{ {
QList< QgsMapDecoration * > decorations; QList< QgsDecorationItem * > decorations;
QString activeDecorations;
Q_FOREACH ( QgsDecorationItem *decoration, mDecorationItems ) Q_FOREACH ( QgsDecorationItem *decoration, mDecorationItems )
{ {
if ( decoration->enabled() ) if ( decoration->enabled() )
{ {
decorations << decoration; decorations << decoration;
if ( activeDecorations.isEmpty() )
activeDecorations = decoration->name().toLower();
else
activeDecorations += QString( ", %1" ).arg( decoration->name().toLower() );
} }
} }
QgsMapSaveDialog dlg( this, mMapCanvas, activeDecorations, QgsMapSaveDialog::Pdf ); QgsMapSaveDialog *dlg = new QgsMapSaveDialog( this, mMapCanvas, decorations, QgsProject::instance()->annotationManager()->annotations(), QgsMapSaveDialog::Pdf );
if ( !dlg.exec() ) dlg->setAttribute( Qt::WA_DeleteOnClose );
return; dlg->show();
QgsSettings settings;
QString lastUsedDir = settings.value( QStringLiteral( "UI/lastSaveAsImageDir" ), QDir::homePath() ).toString();
QString fileName = QFileDialog::getSaveFileName( this, tr( "Save map as" ), lastUsedDir, tr( "PDF Format" ) + " (*.pdf *.PDF)" );
if ( !fileName.isEmpty() )
{
QgsMapSettings ms = QgsMapSettings();
dlg.applyMapSettings( ms );
QPrinter *printer = new QPrinter();
printer->setOutputFileName( fileName );
printer->setOutputFormat( QPrinter::PdfFormat );
printer->setOrientation( QPrinter::Portrait );
// paper size needs to be given in millimeters in order to be able to set a resolution to pass onto the map renderer
printer->setPaperSize( dlg.size() * 25.4 / dlg.dpi(), QPrinter::Millimeter );
printer->setPageMargins( 0, 0, 0, 0, QPrinter::Millimeter );
printer->setResolution( dlg.dpi() );
QPainter *p = new QPainter();
QImage *image = nullptr;
if ( dlg.saveAsRaster() )
{
image = new QImage( dlg.size(), QImage::Format_ARGB32 );
if ( image->isNull() )
{
messageBar()->pushWarning( tr( "Save as PDF" ), tr( "Could not allocate required memory for image" ) );
delete p;
delete image;
delete printer;
return;
}
image->setDotsPerMeterX( 1000 * dlg.dpi() / 25.4 );
image->setDotsPerMeterY( 1000 * dlg.dpi() / 25.4 );
p->begin( image );
}
else
{
p->begin( printer );
}
QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, p );
if ( dlg.drawAnnotations() )
{
mapRendererTask->addAnnotations( QgsProject::instance()->annotationManager()->annotations() );
}
if ( dlg.drawDecorations() )
{
mapRendererTask->addDecorations( decorations );
}
mapRendererTask->setSaveWorldFile( dlg.saveWorldFile() );
connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, this, [ this, p, image, printer ]
{
p->end();
if ( image )
{
QPainter pp;
pp.begin( printer );
QRectF rect( 0, 0, image->width(), image->height() );
pp.drawImage( rect, *image, rect );
pp.end();
}
messageBar()->pushSuccess( tr( "Save as PDF" ), tr( "Successfully saved map to PDF" ) );
delete p;
delete image;
delete printer;
} );
connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, this, [ this, p, image, printer ]( int )
{
delete p;
delete image;
delete printer;
} );
QgsApplication::taskManager()->addTask( mapRendererTask );
}
} // saveMapAsPdf } // saveMapAsPdf
//overloaded version of the above function //overloaded version of the above function

View File

@ -18,25 +18,32 @@
#include "qgsmapsavedialog.h" #include "qgsmapsavedialog.h"
#include "qgis.h" #include "qgis.h"
#include "qgisapp.h"
#include "qgsscalecalculator.h" #include "qgsscalecalculator.h"
#include "qgsdecorationitem.h" #include "qgsdecorationitem.h"
#include "qgsexpressioncontext.h" #include "qgsexpressioncontext.h"
#include "qgsextentgroupbox.h" #include "qgsextentgroupbox.h"
#include "qgsmapsettings.h" #include "qgsmapsettings.h"
#include "qgsmapsettingsutils.h" #include "qgsmapsettingsutils.h"
#include "qgsmaprenderertask.h"
#include "qgsproject.h" #include "qgsproject.h"
#include "qgssettings.h" #include "qgssettings.h"
#include <QCheckBox> #include <QCheckBox>
#include <QSpinBox> #include <QFileDialog>
#include <QImage>
#include <QList> #include <QList>
#include <QPainter>
#include <QPrinter>
#include <QSpinBox>
Q_GUI_EXPORT extern int qt_defaultDpiX(); Q_GUI_EXPORT extern int qt_defaultDpiX();
QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, const QString &activeDecorations, DialogType type ) QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, QList< QgsDecorationItem * > decorations, QList< QgsAnnotation *> annotations, DialogType type )
: QDialog( parent ) : QDialog( parent )
, mDialogType( type ) , mDialogType( type )
, mMapCanvas( mapCanvas ) , mMapCanvas( mapCanvas )
, mAnnotations( annotations )
{ {
setupUi( this ); setupUi( this );
@ -57,6 +64,15 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, co
mScaleWidget->setMapCanvas( mMapCanvas ); mScaleWidget->setMapCanvas( mMapCanvas );
mScaleWidget->setShowCurrentScaleButton( true ); mScaleWidget->setShowCurrentScaleButton( true );
QString activeDecorations;
Q_FOREACH ( QgsDecorationItem *decoration, decorations )
{
mDecorations << decoration;
if ( activeDecorations.isEmpty() )
activeDecorations = decoration->name().toLower();
else
activeDecorations += QString( ", %1" ).arg( decoration->name().toLower() );
}
mDrawDecorations->setText( tr( "Draw active decorations: %1" ).arg( !activeDecorations.isEmpty() ? activeDecorations : tr( "none" ) ) ); mDrawDecorations->setText( tr( "Draw active decorations: %1" ).arg( !activeDecorations.isEmpty() ? activeDecorations : tr( "none" ) ) );
connect( mResolutionSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsMapSaveDialog::updateDpi ); connect( mResolutionSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsMapSaveDialog::updateDpi );
@ -92,6 +108,8 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, co
this->setWindowTitle( tr( "Save map as PDF" ) ); this->setWindowTitle( tr( "Save map as PDF" ) );
} }
connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsMapSaveDialog::accepted );
} }
void QgsMapSaveDialog::updateDpi( int dpi ) void QgsMapSaveDialog::updateDpi( int dpi )
@ -222,3 +240,89 @@ void QgsMapSaveDialog::applyMapSettings( QgsMapSettings &mapSettings )
mapSettings.setExpressionContext( expressionContext ); mapSettings.setExpressionContext( expressionContext );
} }
void QgsMapSaveDialog::accepted()
{
if ( mDialogType == Image )
{
QPair< QString, QString> fileNameAndFilter = QgsGuiUtils::getSaveAsImageName( QgisApp::instance(), tr( "Choose a file name to save the map image as" ) );
if ( fileNameAndFilter.first != QLatin1String( "" ) )
{
QgsMapSettings ms = QgsMapSettings();
applyMapSettings( ms );
QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, fileNameAndFilter.first, fileNameAndFilter.second );
if ( drawAnnotations() )
{
mapRendererTask->addAnnotations( mAnnotations );
}
if ( drawDecorations() )
{
mapRendererTask->addDecorations( mDecorations );
}
mapRendererTask->setSaveWorldFile( saveWorldFile() );
connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, [ = ]
{
QgisApp::instance()->messageBar()->pushSuccess( tr( "Save as image" ), tr( "Successfully saved map to image" ) );
} );
connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, [ = ]( int error )
{
switch ( error )
{
case QgsMapRendererTask::ImageAllocationFail:
{
QgisApp::instance()->messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not allocate required memory for image" ) );
break;
}
case QgsMapRendererTask::ImageSaveFail:
{
QgisApp::instance()->messageBar()->pushWarning( tr( "Save as image" ), tr( "Could not save the map to file" ) );
break;
}
}
} );
QgsApplication::taskManager()->addTask( mapRendererTask );
}
}
else
{
QgsSettings settings;
QString lastUsedDir = settings.value( QStringLiteral( "UI/lastSaveAsImageDir" ), QDir::homePath() ).toString();
QString fileName = QFileDialog::getSaveFileName( QgisApp::instance(), tr( "Save map as" ), lastUsedDir, tr( "PDF Format" ) + " (*.pdf *.PDF)" );
if ( !fileName.isEmpty() )
{
QgsMapSettings ms = QgsMapSettings();
applyMapSettings( ms );
QgsMapRendererTask *mapRendererTask = new QgsMapRendererTask( ms, fileName, QStringLiteral( "PDF" ), saveAsRaster() );
if ( drawAnnotations() )
{
mapRendererTask->addAnnotations( mAnnotations );
}
if ( drawDecorations() )
{
mapRendererTask->addDecorations( mDecorations );
}
mapRendererTask->setSaveWorldFile( saveWorldFile() );
connect( mapRendererTask, &QgsMapRendererTask::renderingComplete, [ = ]
{
QgisApp::instance()->messageBar()->pushSuccess( tr( "Save as PDF" ), tr( "Successfully saved map to PDF" ) );
} );
connect( mapRendererTask, &QgsMapRendererTask::errorOccurred, [ = ]( int )
{
QgisApp::instance()->messageBar()->pushWarning( tr( "Save as PDF" ), tr( "Could not save the map to PDF..." ) );
} );
QgsApplication::taskManager()->addTask( mapRendererTask );
}
}
}

View File

@ -21,8 +21,9 @@
#include "ui_qgsmapsavedialog.h" #include "ui_qgsmapsavedialog.h"
#include "qgisapp.h" #include "qgisapp.h"
#include "qgsrectangle.h"
#include "qgsmapcanvas.h" #include "qgsmapcanvas.h"
#include "qgsmapdecoration.h"
#include "qgsrectangle.h"
#include <QDialog> #include <QDialog>
#include <QSize> #include <QSize>
@ -45,7 +46,10 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog
/** Constructor for QgsMapSaveDialog /** Constructor for QgsMapSaveDialog
*/ */
QgsMapSaveDialog( QWidget *parent = nullptr, QgsMapCanvas *mapCanvas = nullptr, const QString &activeDecorations = QString(), DialogType type = Image ); QgsMapSaveDialog( QWidget *parent = nullptr, QgsMapCanvas *mapCanvas = nullptr,
QList< QgsDecorationItem * > decorations = QList< QgsDecorationItem * >(),
QList< QgsAnnotation *> annotations = QList< QgsAnnotation * >(),
DialogType type = Image );
//! returns extent rectangle //! returns extent rectangle
QgsRectangle extent() const; QgsRectangle extent() const;
@ -73,6 +77,8 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog
private: private:
void accepted();
void updateDpi( int dpi ); void updateDpi( int dpi );
void updateOutputWidth( int width ); void updateOutputWidth( int width );
void updateOutputHeight( int height ); void updateOutputHeight( int height );
@ -82,6 +88,9 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog
DialogType mDialogType; DialogType mDialogType;
QgsMapCanvas *mMapCanvas; QgsMapCanvas *mMapCanvas;
QList< QgsMapDecoration * > mDecorations;
QList< QgsAnnotation *> mAnnotations;
QgsRectangle mExtent; QgsRectangle mExtent;
int mDpi; int mDpi;
QSize mSize; QSize mSize;

View File

@ -22,17 +22,19 @@
#include <QFile> #include <QFile>
#include <QTextStream> #include <QTextStream>
#include <QPrinter>
QgsMapRendererTask::QgsMapRendererTask( const QgsMapSettings &ms, const QString &fileName, const QString &fileFormat ) QgsMapRendererTask::QgsMapRendererTask( const QgsMapSettings &ms, const QString &fileName, const QString &fileFormat, const bool forceRaster )
: QgsTask( tr( "Saving as image" ) ) : QgsTask( tr( "Saving as image" ) )
, mMapSettings( ms ) , mMapSettings( ms )
, mFileName( fileName ) , mFileName( fileName )
, mFileFormat( fileFormat ) , mFileFormat( fileFormat )
, mForceRaster( forceRaster )
{ {
} }
QgsMapRendererTask::QgsMapRendererTask( const QgsMapSettings &ms, QPainter *p ) QgsMapRendererTask::QgsMapRendererTask( const QgsMapSettings &ms, QPainter *p )
: QgsTask( tr( "Saving as image" ) ) : QgsTask( tr( "Rendering to painter" ) )
, mMapSettings( ms ) , mMapSettings( ms )
, mPainter( p ) , mPainter( p )
{ {
@ -70,8 +72,27 @@ bool QgsMapRendererTask::run()
QImage img; QImage img;
std::unique_ptr< QPainter > tempPainter; std::unique_ptr< QPainter > tempPainter;
QPainter *destPainter = mPainter; QPainter *destPainter = mPainter;
std::unique_ptr< QPrinter > printer;
if ( !mPainter ) if ( mFileFormat == QStringLiteral( "PDF" ) )
{
printer.reset( new QPrinter() );
printer->setOutputFileName( mFileName );
printer->setOutputFormat( QPrinter::PdfFormat );
printer->setOrientation( QPrinter::Portrait );
// paper size needs to be given in millimeters in order to be able to set a resolution to pass onto the map renderer
printer->setPaperSize( mMapSettings.outputSize() * 25.4 / mMapSettings.outputDpi(), QPrinter::Millimeter );
printer->setPageMargins( 0, 0, 0, 0, QPrinter::Millimeter );
printer->setResolution( mMapSettings.outputDpi() );
if ( !mForceRaster )
{
tempPainter.reset( new QPainter( printer.get() ) );
destPainter = tempPainter.get();
}
}
if ( !destPainter )
{ {
// save rendered map to an image file // save rendered map to an image file
img = QImage( mMapSettings.outputSize(), QImage::Format_ARGB32 ); img = QImage( mMapSettings.outputSize(), QImage::Format_ARGB32 );
@ -149,6 +170,17 @@ bool QgsMapRendererTask::run()
if ( !mFileName.isEmpty() ) if ( !mFileName.isEmpty() )
{ {
destPainter->end(); destPainter->end();
if ( mForceRaster && mFileFormat == QStringLiteral( "PDF" ) )
{
QPainter pp;
pp.begin( printer.get() );
QRectF rect( 0, 0, img.width(), img.height() );
pp.drawImage( rect, img, rect );
pp.end();
}
else if ( mFileFormat != QStringLiteral( "PDF" ) )
{
bool success = img.save( mFileName, mFileFormat.toLocal8Bit().data() ); bool success = img.save( mFileName, mFileFormat.toLocal8Bit().data() );
if ( !success ) if ( !success )
{ {
@ -173,6 +205,7 @@ bool QgsMapRendererTask::run()
} }
} }
} }
}
return true; return true;
} }

View File

@ -55,7 +55,8 @@ class CORE_EXPORT QgsMapRendererTask : public QgsTask
*/ */
QgsMapRendererTask( const QgsMapSettings &ms, QgsMapRendererTask( const QgsMapSettings &ms,
const QString &fileName, const QString &fileName,
const QString &fileFormat = QString( "PNG" ) ); const QString &fileFormat = QString( "PNG" ),
const bool forceRaster = false );
/** /**
* Constructor for QgsMapRendererTask to render a map to a painter object. * Constructor for QgsMapRendererTask to render a map to a painter object.
@ -108,6 +109,7 @@ class CORE_EXPORT QgsMapRendererTask : public QgsTask
QString mFileName; QString mFileName;
QString mFileFormat; QString mFileFormat;
bool mForceRaster = false;
bool mSaveWorldFile = false; bool mSaveWorldFile = false;
QList< QgsAnnotation * > mAnnotations; QList< QgsAnnotation * > mAnnotations;