[layouts] Resurrect action for exporting to raster images

...this time, without all the useful code locked away in app!
This commit is contained in:
Nyall Dawson 2017-12-11 14:49:11 +10:00
parent 113664fe2e
commit 953d2c437d
8 changed files with 804 additions and 0 deletions

View File

@ -185,6 +185,7 @@ SET(QGIS_APP_SRCS
layout/qgslayoutdesignerdialog.cpp
layout/qgslayoutguidewidget.cpp
layout/qgslayouthtmlwidget.cpp
layout/qgslayoutimageexportoptionsdialog.cpp
layout/qgslayoutitemslistview.cpp
layout/qgslayoutappmenuprovider.cpp
layout/qgslayoutlabelwidget.cpp
@ -403,6 +404,7 @@ SET (QGIS_APP_MOC_HDRS
layout/qgslayoutdesignerdialog.h
layout/qgslayoutguidewidget.h
layout/qgslayouthtmlwidget.h
layout/qgslayoutimageexportoptionsdialog.h
layout/qgslayoutitemslistview.h
layout/qgslayoutlabelwidget.h
layout/qgslayoutlegendwidget.h

View File

@ -33,6 +33,9 @@
#include "qgslayoutviewtoolselect.h"
#include "qgslayoutviewtooleditnodes.h"
#include "qgslayoutitemwidget.h"
#include "qgslayoutimageexportoptionsdialog.h"
#include "qgslayoutitemmap.h"
#include "qgsmessageviewer.h"
#include "qgsgui.h"
#include "qgslayoutitemguiregistry.h"
#include "qgslayoutpropertieswidget.h"
@ -168,6 +171,8 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
connect( mActionLayoutManager, &QAction::triggered, this, &QgsLayoutDesignerDialog::showManager );
connect( mActionRemoveLayout, &QAction::triggered, this, &QgsLayoutDesignerDialog::deleteLayout );
connect( mActionExportAsImage, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportToRaster );
connect( mActionShowGrid, &QAction::triggered, this, &QgsLayoutDesignerDialog::showGrid );
connect( mActionSnapGrid, &QAction::triggered, this, &QgsLayoutDesignerDialog::snapToGrid );
@ -1410,6 +1415,123 @@ void QgsLayoutDesignerDialog::deleteLayout()
close();
}
void QgsLayoutDesignerDialog::exportToRaster()
{
if ( containsWmsLayers() )
showWmsPrintingWarning();
// Image size
double oneInchInLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) );
QSizeF maxPageSize = mLayout->pageCollection()->maximumPageSize();
bool hasUniformPageSizes = mLayout->pageCollection()->hasUniformPageSizes();
int width = ( int )( mLayout->context().dpi() * maxPageSize.width() / oneInchInLayoutUnits );
int height = ( int )( mLayout->context().dpi() * maxPageSize.height() / oneInchInLayoutUnits );
double dpi = mLayout->context().dpi();
int memuse = width * height * 3 / 1000000; // pixmap + image
QgsDebugMsg( QString( "Image %1x%2" ).arg( width ).arg( height ) );
QgsDebugMsg( QString( "memuse = %1" ).arg( memuse ) );
if ( memuse > 400 ) // about 4500x4500
{
int answer = QMessageBox::warning( nullptr, tr( "Export layout" ),
tr( "To create an image of %1x%2 requires about %3 MB of memory. Proceed?" )
.arg( width ).arg( height ).arg( memuse ),
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok );
raise();
if ( answer == QMessageBox::Cancel )
return;
}
//get some defaults from the composition
bool cropToContents = mLayout->customProperty( QStringLiteral( "imageCropToContents" ), false ).toBool();
int marginTop = mLayout->customProperty( QStringLiteral( "imageCropMarginTop" ), 0 ).toInt();
int marginRight = mLayout->customProperty( QStringLiteral( "imageCropMarginRight" ), 0 ).toInt();
int marginBottom = mLayout->customProperty( QStringLiteral( "imageCropMarginBottom" ), 0 ).toInt();
int marginLeft = mLayout->customProperty( QStringLiteral( "imageCropMarginLeft" ), 0 ).toInt();
QgsLayoutImageExportOptionsDialog imageDlg( this );
imageDlg.setImageSize( maxPageSize );
imageDlg.setResolution( dpi );
imageDlg.setCropToContents( cropToContents );
imageDlg.setCropMargins( marginTop, marginRight, marginBottom, marginLeft );
#if 0 //TODO
QgsAtlasComposition *atlasMap = &mComposition->atlasComposition();
#endif
QString outputFileName;
#if 0 //TODO
if ( atlasMap->enabled() && mComposition->atlasMode() == QgsComposition::PreviewAtlas )
{
QString lastUsedDir = settings.value( QStringLiteral( "UI/lastSaveAsImageDir" ), QDir::homePath() ).toString();
outputFileName = QDir( lastUsedDir ).filePath( atlasMap->currentFilename() );
}
#endif
#ifdef Q_OS_MAC
mQgis->activateWindow();
this->raise();
#endif
QPair<QString, QString> fileNExt = QgsGuiUtils::getSaveAsImageName( this, tr( "Save layout as" ), outputFileName );
this->activateWindow();
if ( fileNExt.first.isEmpty() )
{
return;
}
if ( !imageDlg.exec() )
return;
cropToContents = imageDlg.cropToContents();
imageDlg.getCropMargins( marginTop, marginRight, marginBottom, marginLeft );
mLayout->setCustomProperty( QStringLiteral( "imageCropToContents" ), cropToContents );
mLayout->setCustomProperty( QStringLiteral( "imageCropMarginTop" ), marginTop );
mLayout->setCustomProperty( QStringLiteral( "imageCropMarginRight" ), marginRight );
mLayout->setCustomProperty( QStringLiteral( "imageCropMarginBottom" ), marginBottom );
mLayout->setCustomProperty( QStringLiteral( "imageCropMarginLeft" ), marginLeft );
mView->setPaintingEnabled( false );
QgsLayoutExporter exporter( mLayout );
QgsLayoutExporter::ImageExportSettings settings;
settings.cropToContents = cropToContents;
settings.cropMargins = QgsMargins( marginLeft, marginTop, marginRight, marginBottom );
settings.dpi = imageDlg.resolution();
if ( hasUniformPageSizes )
{
settings.imageSize = QSize( imageDlg.imageWidth(), imageDlg.imageHeight() );
}
settings.generateWorldFile = mLayout->customProperty( QStringLiteral( "exportWorldFile" ), false ).toBool();
switch ( exporter.exportToImage( fileNExt.first, settings ) )
{
case QgsLayoutExporter::Success:
break;
case QgsLayoutExporter::FileError:
QMessageBox::warning( this, tr( "Image Export Error" ),
QString( tr( "Cannot write to %1.\n\nThis file may be open in another application." ) ).arg( exporter.errorFile() ),
QMessageBox::Ok,
QMessageBox::Ok );
break;
case QgsLayoutExporter::MemoryError:
QMessageBox::warning( nullptr, tr( "Memory Allocation Error" ),
tr( "Trying to create image %1 (%2×%3 @ %4dpi ) "
"resulted in a memory overflow.\n\n"
"Please try a lower resolution or a smaller paper size." )
.arg( exporter.errorFile() ).arg( imageDlg.imageWidth() ).arg( imageDlg.imageHeight() ).arg( settings.dpi ),
QMessageBox::Ok, QMessageBox::Ok );
break;
}
mView->setPaintingEnabled( true );
}
void QgsLayoutDesignerDialog::paste()
{
QPointF pt = mView->mapFromGlobal( QCursor::pos() );
@ -1525,6 +1647,36 @@ void QgsLayoutDesignerDialog::initializeRegistry()
}
bool QgsLayoutDesignerDialog::containsWmsLayers() const
{
QList< QgsLayoutItemMap *> maps;
mLayout->layoutItems( maps );
for ( QgsLayoutItemMap *map : qgis::as_const( maps ) )
{
if ( map->containsWmsLayer() )
return true;
}
return false;
}
void QgsLayoutDesignerDialog::showWmsPrintingWarning()
{
QgsSettings settings;
bool displayWMSWarning = settings.value( QStringLiteral( "/UI/displayComposerWMSWarning" ), true ).toBool();
if ( displayWMSWarning )
{
QgsMessageViewer *m = new QgsMessageViewer( this );
m->setWindowTitle( tr( "Project Contains WMS Layers" ) );
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 );
m->setCheckBoxText( tr( "Don't show this message again" ) );
m->setCheckBoxState( Qt::Unchecked );
m->setCheckBoxVisible( true );
m->setCheckBoxQgsSettingsLabel( QStringLiteral( "/UI/displayComposerWMSWarning" ) );
m->exec(); //deleted on close
}
}
void QgsLayoutDesignerDialog::selectItems( const QList<QgsLayoutItem *> items )
{
for ( QGraphicsItem *item : items )

View File

@ -274,6 +274,7 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
void showManager();
void renameLayout();
void deleteLayout();
void exportToRaster();
private:
@ -360,6 +361,10 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
void initializeRegistry();
bool containsWmsLayers() const;
//! Displays a warning because of possible min/max size in WMS
void showWmsPrintingWarning();
};
#endif // QGSLAYOUTDESIGNERDIALOG_H

View File

@ -0,0 +1,167 @@
/***************************************************************************
qgslayoutimageexportoptionsdialog.cpp
-------------------------------------
begin : December 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson 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 "qgslayoutimageexportoptionsdialog.h"
#include "qgis.h"
#include "qgssettings.h"
#include "qgsgui.h"
#include <QCheckBox>
#include <QPushButton>
QgsLayoutImageExportOptionsDialog::QgsLayoutImageExportOptionsDialog( QWidget *parent, Qt::WindowFlags flags )
: QDialog( parent, flags )
{
setupUi( this );
connect( mWidthSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutImageExportOptionsDialog::mWidthSpinBox_valueChanged );
connect( mHeightSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutImageExportOptionsDialog::mHeightSpinBox_valueChanged );
connect( mResolutionSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutImageExportOptionsDialog::mResolutionSpinBox_valueChanged );
connect( mClipToContentGroupBox, &QGroupBox::toggled, this, &QgsLayoutImageExportOptionsDialog::clipToContentsToggled );
QgsGui::instance()->enableAutoGeometryRestore( this );
}
void QgsLayoutImageExportOptionsDialog::setResolution( double resolution )
{
mResolutionSpinBox->setValue( resolution );
if ( mImageSize.isValid() )
{
mWidthSpinBox->blockSignals( true );
mHeightSpinBox->blockSignals( true );
if ( mClipToContentGroupBox->isChecked() )
{
mWidthSpinBox->setValue( 0 );
mHeightSpinBox->setValue( 0 );
}
else
{
mWidthSpinBox->setValue( mImageSize.width() * resolution / 25.4 );
mHeightSpinBox->setValue( mImageSize.height() * resolution / 25.4 );
}
mWidthSpinBox->blockSignals( false );
mHeightSpinBox->blockSignals( false );
}
}
double QgsLayoutImageExportOptionsDialog::resolution() const
{
return mResolutionSpinBox->value();
}
void QgsLayoutImageExportOptionsDialog::setImageSize( QSizeF size )
{
mImageSize = size;
mWidthSpinBox->blockSignals( true );
mHeightSpinBox->blockSignals( true );
mWidthSpinBox->setValue( size.width() * mResolutionSpinBox->value() / 25.4 );
mHeightSpinBox->setValue( size.height() * mResolutionSpinBox->value() / 25.4 );
mWidthSpinBox->blockSignals( false );
mHeightSpinBox->blockSignals( false );
}
int QgsLayoutImageExportOptionsDialog::imageWidth() const
{
return mWidthSpinBox->value();
}
int QgsLayoutImageExportOptionsDialog::imageHeight() const
{
return mHeightSpinBox->value();
}
void QgsLayoutImageExportOptionsDialog::setCropToContents( bool crop )
{
mClipToContentGroupBox->setChecked( crop );
}
bool QgsLayoutImageExportOptionsDialog::cropToContents() const
{
return mClipToContentGroupBox->isChecked();
}
void QgsLayoutImageExportOptionsDialog::getCropMargins( int &topMargin, int &rightMargin, int &bottomMargin, int &leftMargin ) const
{
topMargin = mTopMarginSpinBox->value();
rightMargin = mRightMarginSpinBox->value();
bottomMargin = mBottomMarginSpinBox->value();
leftMargin = mLeftMarginSpinBox->value();
}
void QgsLayoutImageExportOptionsDialog::setCropMargins( int topMargin, int rightMargin, int bottomMargin, int leftMargin )
{
mTopMarginSpinBox->setValue( topMargin );
mRightMarginSpinBox->setValue( rightMargin );
mBottomMarginSpinBox->setValue( bottomMargin );
mLeftMarginSpinBox->setValue( leftMargin );
}
void QgsLayoutImageExportOptionsDialog::mWidthSpinBox_valueChanged( int value )
{
mHeightSpinBox->blockSignals( true );
mResolutionSpinBox->blockSignals( true );
mHeightSpinBox->setValue( mImageSize.height() * value / mImageSize.width() );
mResolutionSpinBox->setValue( value * 25.4 / mImageSize.width() );
mHeightSpinBox->blockSignals( false );
mResolutionSpinBox->blockSignals( false );
}
void QgsLayoutImageExportOptionsDialog::mHeightSpinBox_valueChanged( int value )
{
mWidthSpinBox->blockSignals( true );
mResolutionSpinBox->blockSignals( true );
mWidthSpinBox->setValue( mImageSize.width() * value / mImageSize.height() );
mResolutionSpinBox->setValue( value * 25.4 / mImageSize.height() );
mWidthSpinBox->blockSignals( false );
mResolutionSpinBox->blockSignals( false );
}
void QgsLayoutImageExportOptionsDialog::mResolutionSpinBox_valueChanged( int value )
{
mWidthSpinBox->blockSignals( true );
mHeightSpinBox->blockSignals( true );
if ( mClipToContentGroupBox->isChecked() )
{
mWidthSpinBox->setValue( 0 );
mHeightSpinBox->setValue( 0 );
}
else
{
mWidthSpinBox->setValue( mImageSize.width() * value / 25.4 );
mHeightSpinBox->setValue( mImageSize.height() * value / 25.4 );
}
mWidthSpinBox->blockSignals( false );
mHeightSpinBox->blockSignals( false );
}
void QgsLayoutImageExportOptionsDialog::clipToContentsToggled( bool state )
{
mWidthSpinBox->setEnabled( !state );
mHeightSpinBox->setEnabled( !state );
if ( state )
{
whileBlocking( mWidthSpinBox )->setValue( 0 );
whileBlocking( mHeightSpinBox )->setValue( 0 );
}
else
{
whileBlocking( mWidthSpinBox )->setValue( mImageSize.width() * mResolutionSpinBox->value() / 25.4 );
whileBlocking( mHeightSpinBox )->setValue( mImageSize.height() * mResolutionSpinBox->value() / 25.4 );
}
}

View File

@ -0,0 +1,119 @@
/***************************************************************************
qgslayoutimageexportoptionsdialog.h
-------------------------------------
begin : December 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson 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 QGSLAYOUTIMAGEEXPORTOPTIONSDIALOG_H
#define QGSLAYOUTIMAGEEXPORTOPTIONSDIALOG_H
#include <QDialog>
#include "ui_qgslayoutimageexportoptions.h"
/**
* A dialog for customising the properties of an exported image file.
*/
class QgsLayoutImageExportOptionsDialog: public QDialog, private Ui::QgsLayoutImageExportOptionsDialog
{
Q_OBJECT
public:
/**
* Constructor for QgsLayoutImageExportOptionsDialog
* \param parent parent widget
* \param flags window flags
*/
QgsLayoutImageExportOptionsDialog( QWidget *parent = nullptr, Qt::WindowFlags flags = 0 );
/**
* Sets the initial resolution displayed in the dialog.
* \param resolution default resolution in DPI
* \see resolution()
*/
void setResolution( double resolution );
/**
* Returns the selected resolution from the dialog.
* \returns image resolution in DPI
* \see setResolution()
*/
double resolution() const;
/**
* Sets the target image size. This is used to calculate the default size in pixels
* and also for determining the image's width to height ratio.
* \param size image size
*/
void setImageSize( QSizeF size );
/**
* Returns the user-set image width in pixels.
* \see imageHeight
*/
int imageWidth() const;
/**
* Returns the user-set image height in pixels.
* \see imageWidth
*/
int imageHeight() const;
/**
* Sets whether the crop to contents option should be checked in the dialog
* \param crop set to true to check crop to contents
* \see cropToContents()
*/
void setCropToContents( bool crop );
/**
* Returns whether the crop to contents option is checked in the dialog.
* \see setCropToContents()
*/
bool cropToContents() const;
/**
* Fetches the current crop to contents margin values, in pixels.
* \param topMargin destination for top margin
* \param rightMargin destination for right margin
* \param bottomMargin destination for bottom margin
* \param leftMargin destination for left margin
*/
void getCropMargins( int &topMargin, int &rightMargin, int &bottomMargin, int &leftMargin ) const;
/**
* Sets the current crop to contents margin values, in pixels.
* \param topMargin top margin
* \param rightMargin right margin
* \param bottomMargin bottom margin
* \param leftMargin left margin
*/
void setCropMargins( int topMargin, int rightMargin, int bottomMargin, int leftMargin );
private slots:
void mWidthSpinBox_valueChanged( int value );
void mHeightSpinBox_valueChanged( int value );
void mResolutionSpinBox_valueChanged( int value );
void clipToContentsToggled( bool state );
private:
QSizeF mImageSize;
};
#endif // QGSLAYOUTIMAGEEXPORTOPTIONSDIALOG_H

View File

@ -56,6 +56,7 @@ QgsLayoutPropertiesWidget::QgsLayoutPropertiesWidget( QWidget *parent, QgsLayout
bool exportWorldFile = mLayout->customProperty( QStringLiteral( "exportWorldFile" ), false ).toBool();
mGenerateWorldFileCheckBox->setChecked( exportWorldFile );
connect( mGenerateWorldFileCheckBox, &QCheckBox::toggled, this, &QgsLayoutPropertiesWidget::worldFileToggled );
mTopMarginSpinBox->setValue( topMargin );
mMarginUnitsComboBox->linkToWidget( mTopMarginSpinBox );

View File

@ -69,6 +69,10 @@
<addaction name="mActionLayoutManager"/>
<addaction name="mActionLoadFromTemplate"/>
<addaction name="mActionSaveAsTemplate"/>
<addaction name="separator"/>
<addaction name="mActionExportAsImage"/>
<addaction name="mActionExportAsSVG"/>
<addaction name="mActionExportAsPDF"/>
</widget>
<widget class="QToolBar" name="mToolsToolbar">
<property name="windowTitle">
@ -113,6 +117,10 @@
<addaction name="mActionLoadFromTemplate"/>
<addaction name="mActionSaveAsTemplate"/>
<addaction name="separator"/>
<addaction name="mActionExportAsImage"/>
<addaction name="mActionExportAsSVG"/>
<addaction name="mActionExportAsPDF"/>
<addaction name="separator"/>
<addaction name="mActionClose"/>
</widget>
<widget class="QMenu" name="mItemMenu">
@ -1173,6 +1181,36 @@
<string>Delete layout</string>
</property>
</action>
<action name="mActionExportAsImage">
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</iconset>
</property>
<property name="text">
<string>Export as &amp;Image…</string>
</property>
<property name="toolTip">
<string>Export as image</string>
</property>
</action>
<action name="mActionExportAsPDF">
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionSaveAsPDF.svg</normaloff>:/images/themes/default/mActionSaveAsPDF.svg</iconset>
</property>
<property name="text">
<string>&amp;Export as PDF…</string>
</property>
</action>
<action name="mActionExportAsSVG">
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionSaveAsSVG.svg</normaloff>:/images/themes/default/mActionSaveAsSVG.svg</iconset>
</property>
<property name="text">
<string>Export as S&amp;VG…</string>
</property>
</action>
</widget>
<resources>
<include location="../../../images/images.qrc"/>

View File

@ -0,0 +1,320 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsLayoutImageExportOptionsDialog</class>
<widget class="QDialog" name="QgsLayoutImageExportOptionsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>533</width>
<height>651</height>
</rect>
</property>
<property name="windowTitle">
<string>Image Export Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Export options</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Export resolution</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Page height</string>
</property>
</widget>
</item>
<item row="0" column="2" colspan="2">
<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="2" colspan="2">
<widget class="QgsSpinBox" name="mWidthSpinBox">
<property name="specialValueText">
<string>Auto</string>
</property>
<property name="suffix">
<string> px</string>
</property>
<property name="prefix">
<string/>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>99999999</number>
</property>
<property name="showClearButton" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Page width</string>
</property>
</widget>
</item>
<item row="2" column="2" colspan="2">
<widget class="QgsSpinBox" name="mHeightSpinBox">
<property name="specialValueText">
<string>Auto</string>
</property>
<property name="suffix">
<string> px</string>
</property>
<property name="prefix">
<string/>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>99999999</number>
</property>
<property name="showClearButton" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="4">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QgsCollapsibleGroupBoxBasic" name="mClipToContentGroupBox">
<property name="title">
<string>Crop to content</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QGridLayout" name="gridLayout_5">
<item row="1" column="0" colspan="4">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Left</string>
</property>
</widget>
</item>
<item>
<widget class="QgsSpinBox" name="mLeftMarginSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_11">
<property name="text">
<string>Right</string>
</property>
</widget>
</item>
<item>
<widget class="QgsSpinBox" name="mRightMarginSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Bottom</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Top margin</string>
</property>
</widget>
</item>
<item row="0" column="3">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="2">
<widget class="QgsSpinBox" name="mTopMarginSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QgsSpinBox" name="mBottomMarginSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</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>QgsCollapsibleGroupBoxBasic</class>
<extends>QGroupBox</extends>
<header>qgscollapsiblegroupbox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsSpinBox</class>
<extends>QSpinBox</extends>
<header>qgsspinbox.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mResolutionSpinBox</tabstop>
<tabstop>mWidthSpinBox</tabstop>
<tabstop>mHeightSpinBox</tabstop>
<tabstop>mClipToContentGroupBox</tabstop>
<tabstop>mTopMarginSpinBox</tabstop>
<tabstop>mLeftMarginSpinBox</tabstop>
<tabstop>mRightMarginSpinBox</tabstop>
<tabstop>mBottomMarginSpinBox</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>QgsLayoutImageExportOptionsDialog</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>QgsLayoutImageExportOptionsDialog</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>