mirror of
https://github.com/qgis/QGIS.git
synced 2025-03-31 00:03:42 -04:00
whenever exporting a layout to PDF This matches the behavior with SVG and raster exports, and allows users to set specific properties for the export (such as text rendering format) Fixes #8844
4474 lines
156 KiB
C++
4474 lines
156 KiB
C++
/***************************************************************************
|
||
qgslayoutdesignerdialog.cpp
|
||
---------------------------
|
||
begin : July 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 "qgslayoutdesignerdialog.h"
|
||
#include "qgslayoutitemregistry.h"
|
||
#include "qgssettings.h"
|
||
#include "qgisapp.h"
|
||
#include "qgsfileutils.h"
|
||
#include "qgslogger.h"
|
||
#include "qgslayout.h"
|
||
#include "qgslayoutatlas.h"
|
||
#include "qgslayoutappmenuprovider.h"
|
||
#include "qgslayoutcustomdrophandler.h"
|
||
#include "qgslayoutmanager.h"
|
||
#include "qgslayoutview.h"
|
||
#include "qgslayoutviewtooladditem.h"
|
||
#include "qgslayoutviewtooladdnodeitem.h"
|
||
#include "qgslayoutviewtoolpan.h"
|
||
#include "qgslayoutviewtoolmoveitemcontent.h"
|
||
#include "qgslayoutviewtoolzoom.h"
|
||
#include "qgslayoutviewtoolselect.h"
|
||
#include "qgslayoutviewtooleditnodes.h"
|
||
#include "qgslayoutitemwidget.h"
|
||
#include "qgslayoutimageexportoptionsdialog.h"
|
||
#include "qgslayoutitemmap.h"
|
||
#include "qgsprintlayout.h"
|
||
#include "qgsmapcanvas.h"
|
||
#include "qgsrendercontext.h"
|
||
#include "qgsmessagebar.h"
|
||
#include "qgsmessageviewer.h"
|
||
#include "qgsgui.h"
|
||
#include "qgsfeedback.h"
|
||
#include "qgslayoutitemguiregistry.h"
|
||
#include "qgslayoutpropertieswidget.h"
|
||
#include "qgslayoutruler.h"
|
||
#include "qgslayoutaddpagesdialog.h"
|
||
#include "qgspanelwidgetstack.h"
|
||
#include "qgspanelwidget.h"
|
||
#include "qgsdockwidget.h"
|
||
#include "qgslayoutpagepropertieswidget.h"
|
||
#include "qgslayoutguidewidget.h"
|
||
#include "qgslayoutmousehandles.h"
|
||
#include "qgslayoutmodel.h"
|
||
#include "qgslayoutitemslistview.h"
|
||
#include "qgsproject.h"
|
||
#include "qgsbusyindicatordialog.h"
|
||
#include "qgslayoutundostack.h"
|
||
#include "qgslayoutatlaswidget.h"
|
||
#include "qgslayoutpagecollection.h"
|
||
#include "qgsreport.h"
|
||
#include "qgsreportorganizerwidget.h"
|
||
#include "qgsreadwritecontext.h"
|
||
#include "ui_qgssvgexportoptions.h"
|
||
#include "ui_qgspdfexportoptions.h"
|
||
#include "qgsproxyprogresstask.h"
|
||
#include "ui_defaults.h"
|
||
|
||
#include <QShortcut>
|
||
#include <QComboBox>
|
||
#include <QLineEdit>
|
||
#include <QDesktopWidget>
|
||
#include <QSlider>
|
||
#include <QLabel>
|
||
#include <QUndoView>
|
||
#include <QTreeView>
|
||
#include <QFileDialog>
|
||
#include <QMessageBox>
|
||
#include <QProgressDialog>
|
||
#include <QPrinter>
|
||
#include <QPrintDialog>
|
||
#include <QPageSetupDialog>
|
||
#include <QWidgetAction>
|
||
#ifdef Q_OS_MACX
|
||
#include <ApplicationServices/ApplicationServices.h>
|
||
#endif
|
||
|
||
#ifdef ENABLE_MODELTEST
|
||
#include "modeltest.h"
|
||
#endif
|
||
|
||
//add some nice zoom levels for zoom comboboxes
|
||
QList<double> QgsLayoutDesignerDialog::sStatusZoomLevelsList { 0.125, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0};
|
||
#define FIT_LAYOUT -101
|
||
#define FIT_LAYOUT_WIDTH -102
|
||
|
||
bool QgsLayoutDesignerDialog::sInitializedRegistry = false;
|
||
|
||
QgsAppLayoutDesignerInterface::QgsAppLayoutDesignerInterface( QgsLayoutDesignerDialog *dialog )
|
||
: QgsLayoutDesignerInterface( dialog )
|
||
, mDesigner( dialog )
|
||
{}
|
||
|
||
QWidget *QgsAppLayoutDesignerInterface::window()
|
||
{
|
||
return mDesigner;
|
||
}
|
||
|
||
QgsLayout *QgsAppLayoutDesignerInterface::layout()
|
||
{
|
||
return mDesigner->currentLayout();
|
||
}
|
||
|
||
QgsMasterLayoutInterface *QgsAppLayoutDesignerInterface::masterLayout()
|
||
{
|
||
return mDesigner->masterLayout();
|
||
}
|
||
|
||
QgsLayoutView *QgsAppLayoutDesignerInterface::view()
|
||
{
|
||
return mDesigner->view();
|
||
}
|
||
|
||
QgsMessageBar *QgsAppLayoutDesignerInterface::messageBar()
|
||
{
|
||
return mDesigner->messageBar();
|
||
}
|
||
|
||
void QgsAppLayoutDesignerInterface::selectItems( const QList<QgsLayoutItem *> &items )
|
||
{
|
||
mDesigner->selectItems( items );
|
||
}
|
||
|
||
void QgsAppLayoutDesignerInterface::setAtlasPreviewEnabled( bool enabled )
|
||
{
|
||
mDesigner->setAtlasPreviewEnabled( enabled );
|
||
}
|
||
|
||
bool QgsAppLayoutDesignerInterface::atlasPreviewEnabled() const
|
||
{
|
||
return mDesigner->atlasPreviewEnabled();
|
||
}
|
||
|
||
void QgsAppLayoutDesignerInterface::showItemOptions( QgsLayoutItem *item, bool bringPanelToFront )
|
||
{
|
||
mDesigner->showItemOptions( item, bringPanelToFront );
|
||
}
|
||
|
||
QMenu *QgsAppLayoutDesignerInterface::layoutMenu()
|
||
{
|
||
return mDesigner->mLayoutMenu;
|
||
}
|
||
|
||
QMenu *QgsAppLayoutDesignerInterface::editMenu()
|
||
{
|
||
return mDesigner->menuEdit;
|
||
}
|
||
|
||
QMenu *QgsAppLayoutDesignerInterface::viewMenu()
|
||
{
|
||
return mDesigner->mMenuView;
|
||
}
|
||
|
||
QMenu *QgsAppLayoutDesignerInterface::itemsMenu()
|
||
{
|
||
return mDesigner->menuLayout;
|
||
}
|
||
|
||
QMenu *QgsAppLayoutDesignerInterface::atlasMenu()
|
||
{
|
||
return mDesigner->mMenuAtlas;
|
||
}
|
||
|
||
QMenu *QgsAppLayoutDesignerInterface::reportMenu()
|
||
{
|
||
return mDesigner->mMenuReport;
|
||
}
|
||
|
||
QMenu *QgsAppLayoutDesignerInterface::settingsMenu()
|
||
{
|
||
return mDesigner->menuSettings;
|
||
}
|
||
|
||
QToolBar *QgsAppLayoutDesignerInterface::layoutToolbar()
|
||
{
|
||
return mDesigner->mLayoutToolbar;
|
||
}
|
||
|
||
QToolBar *QgsAppLayoutDesignerInterface::navigationToolbar()
|
||
{
|
||
return mDesigner->mNavigationToolbar;
|
||
}
|
||
|
||
QToolBar *QgsAppLayoutDesignerInterface::actionsToolbar()
|
||
{
|
||
return mDesigner->mActionsToolbar;
|
||
}
|
||
|
||
QToolBar *QgsAppLayoutDesignerInterface::atlasToolbar()
|
||
{
|
||
return mDesigner->mAtlasToolbar;
|
||
}
|
||
|
||
void QgsAppLayoutDesignerInterface::addDockWidget( Qt::DockWidgetArea area, QDockWidget *dock )
|
||
{
|
||
mDesigner->addDockWidget( area, dock );
|
||
}
|
||
|
||
void QgsAppLayoutDesignerInterface::removeDockWidget( QDockWidget *dock )
|
||
{
|
||
mDesigner->removeDockWidget( dock );
|
||
}
|
||
|
||
void QgsAppLayoutDesignerInterface::close()
|
||
{
|
||
mDesigner->close();
|
||
}
|
||
|
||
void QgsAppLayoutDesignerInterface::showRulers( bool visible )
|
||
{
|
||
mDesigner->showRulers( visible );
|
||
}
|
||
|
||
|
||
static bool cmpByText_( QAction *a, QAction *b )
|
||
{
|
||
return QString::localeAwareCompare( a->text(), b->text() ) < 0;
|
||
}
|
||
|
||
|
||
QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFlags flags )
|
||
: QMainWindow( parent, flags )
|
||
, mInterface( new QgsAppLayoutDesignerInterface( this ) )
|
||
, mToolsActionGroup( new QActionGroup( this ) )
|
||
{
|
||
if ( !sInitializedRegistry )
|
||
{
|
||
initializeRegistry();
|
||
}
|
||
QgsSettings settings;
|
||
int size = settings.value( QStringLiteral( "/qgis/iconSize" ), QGIS_ICON_SIZE ).toInt();
|
||
setIconSize( QSize( size, size ) );
|
||
setStyleSheet( QgisApp::instance()->styleSheet() );
|
||
|
||
setupUi( this );
|
||
setWindowTitle( tr( "QGIS Layout Designer" ) );
|
||
setAcceptDrops( true );
|
||
|
||
setAttribute( Qt::WA_DeleteOnClose );
|
||
setDockOptions( dockOptions() | QMainWindow::GroupedDragging );
|
||
|
||
//create layout view
|
||
QGridLayout *viewLayout = new QGridLayout();
|
||
viewLayout->setSpacing( 0 );
|
||
viewLayout->setMargin( 0 );
|
||
viewLayout->setContentsMargins( 0, 0, 0, 0 );
|
||
centralWidget()->layout()->setSpacing( 0 );
|
||
centralWidget()->layout()->setMargin( 0 );
|
||
centralWidget()->layout()->setContentsMargins( 0, 0, 0, 0 );
|
||
|
||
mMessageBar = new QgsMessageBar( centralWidget() );
|
||
mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
|
||
static_cast< QGridLayout * >( centralWidget()->layout() )->addWidget( mMessageBar, 0, 0, 1, 1, Qt::AlignTop );
|
||
|
||
mHorizontalRuler = new QgsLayoutRuler( nullptr, Qt::Horizontal );
|
||
mVerticalRuler = new QgsLayoutRuler( nullptr, Qt::Vertical );
|
||
mRulerLayoutFix = new QWidget();
|
||
mRulerLayoutFix->setAttribute( Qt::WA_NoMousePropagation );
|
||
mRulerLayoutFix->setBackgroundRole( QPalette::Window );
|
||
mRulerLayoutFix->setFixedSize( mVerticalRuler->rulerSize(), mHorizontalRuler->rulerSize() );
|
||
viewLayout->addWidget( mRulerLayoutFix, 0, 0 );
|
||
viewLayout->addWidget( mHorizontalRuler, 0, 1 );
|
||
viewLayout->addWidget( mVerticalRuler, 1, 0 );
|
||
|
||
//initial state of rulers
|
||
bool showRulers = settings.value( QStringLiteral( "LayoutDesigner/showRulers" ), true, QgsSettings::App ).toBool();
|
||
mActionShowRulers->setChecked( showRulers );
|
||
mHorizontalRuler->setVisible( showRulers );
|
||
mVerticalRuler->setVisible( showRulers );
|
||
mRulerLayoutFix->setVisible( showRulers );
|
||
mActionShowRulers->blockSignals( false );
|
||
connect( mActionShowRulers, &QAction::triggered, this, &QgsLayoutDesignerDialog::showRulers );
|
||
|
||
QMenu *rulerMenu = new QMenu( this );
|
||
rulerMenu->addAction( mActionShowGuides );
|
||
rulerMenu->addAction( mActionSnapGuides );
|
||
rulerMenu->addAction( mActionManageGuides );
|
||
rulerMenu->addAction( mActionClearGuides );
|
||
rulerMenu->addSeparator();
|
||
rulerMenu->addAction( mActionShowRulers );
|
||
mHorizontalRuler->setContextMenu( rulerMenu );
|
||
mVerticalRuler->setContextMenu( rulerMenu );
|
||
|
||
connect( mActionRefreshView, &QAction::triggered, this, &QgsLayoutDesignerDialog::refreshLayout );
|
||
connect( mActionSaveProject, &QAction::triggered, this, &QgsLayoutDesignerDialog::saveProject );
|
||
connect( mActionNewLayout, &QAction::triggered, this, &QgsLayoutDesignerDialog::newLayout );
|
||
connect( mActionLayoutManager, &QAction::triggered, this, &QgsLayoutDesignerDialog::showManager );
|
||
connect( mActionRemoveLayout, &QAction::triggered, this, &QgsLayoutDesignerDialog::deleteLayout );
|
||
|
||
connect( mActionPrint, &QAction::triggered, this, &QgsLayoutDesignerDialog::print );
|
||
connect( mActionExportAsImage, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportToRaster );
|
||
connect( mActionExportAsPDF, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportToPdf );
|
||
connect( mActionExportAsSVG, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportToSvg );
|
||
|
||
connect( mActionShowGrid, &QAction::triggered, this, &QgsLayoutDesignerDialog::showGrid );
|
||
connect( mActionSnapGrid, &QAction::triggered, this, &QgsLayoutDesignerDialog::snapToGrid );
|
||
|
||
connect( mActionShowGuides, &QAction::triggered, this, &QgsLayoutDesignerDialog::showGuides );
|
||
connect( mActionSnapGuides, &QAction::triggered, this, &QgsLayoutDesignerDialog::snapToGuides );
|
||
connect( mActionSmartGuides, &QAction::triggered, this, &QgsLayoutDesignerDialog::snapToItems );
|
||
|
||
connect( mActionShowBoxes, &QAction::triggered, this, &QgsLayoutDesignerDialog::showBoxes );
|
||
connect( mActionShowPage, &QAction::triggered, this, &QgsLayoutDesignerDialog::showPages );
|
||
|
||
connect( mActionPasteInPlace, &QAction::triggered, this, &QgsLayoutDesignerDialog::pasteInPlace );
|
||
connect( mActionAtlasPreview, &QAction::triggered, this, &QgsLayoutDesignerDialog::atlasPreviewTriggered );
|
||
connect( mActionAtlasNext, &QAction::triggered, this, &QgsLayoutDesignerDialog::atlasNext );
|
||
connect( mActionAtlasPrev, &QAction::triggered, this, &QgsLayoutDesignerDialog::atlasPrevious );
|
||
connect( mActionAtlasFirst, &QAction::triggered, this, &QgsLayoutDesignerDialog::atlasFirst );
|
||
connect( mActionAtlasLast, &QAction::triggered, this, &QgsLayoutDesignerDialog::atlasLast );
|
||
connect( mActionPrintAtlas, &QAction::triggered, this, &QgsLayoutDesignerDialog::printAtlas );
|
||
connect( mActionExportAtlasAsImage, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportAtlasToRaster );
|
||
connect( mActionExportAtlasAsSVG, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportAtlasToSvg );
|
||
connect( mActionExportAtlasAsPDF, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportAtlasToPdf );
|
||
|
||
connect( mActionExportReportAsImage, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportReportToRaster );
|
||
connect( mActionExportReportAsSVG, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportReportToSvg );
|
||
connect( mActionExportReportAsPDF, &QAction::triggered, this, &QgsLayoutDesignerDialog::exportReportToPdf );
|
||
connect( mActionPrintReport, &QAction::triggered, this, &QgsLayoutDesignerDialog::printReport );
|
||
|
||
connect( mActionPageSetup, &QAction::triggered, this, &QgsLayoutDesignerDialog::pageSetup );
|
||
|
||
connect( mActionOptions, &QAction::triggered, this, [ = ]
|
||
{
|
||
QgisApp::instance()->showOptionsDialog( this, QStringLiteral( "mOptionsPageComposer" ) );
|
||
} );
|
||
|
||
mView = new QgsLayoutView();
|
||
//mView->setMapCanvas( mQgis->mapCanvas() );
|
||
mView->setContentsMargins( 0, 0, 0, 0 );
|
||
mView->setHorizontalRuler( mHorizontalRuler );
|
||
mView->setVerticalRuler( mVerticalRuler );
|
||
viewLayout->addWidget( mView, 1, 1 );
|
||
//view does not accept focus via tab
|
||
mView->setFocusPolicy( Qt::ClickFocus );
|
||
mViewFrame->setLayout( viewLayout );
|
||
mViewFrame->setContentsMargins( 0, 0, 0, 1 ); // 1 is deliberate!
|
||
mView->setFrameShape( QFrame::NoFrame );
|
||
|
||
connect( mActionClose, &QAction::triggered, this, &QWidget::close );
|
||
|
||
// populate with initial items...
|
||
const QList< int > itemMetadataIds = QgsGui::layoutItemGuiRegistry()->itemMetadataIds();
|
||
for ( int id : itemMetadataIds )
|
||
{
|
||
itemTypeAdded( id );
|
||
}
|
||
//..and listen out for new item types
|
||
connect( QgsGui::layoutItemGuiRegistry(), &QgsLayoutItemGuiRegistry::typeAdded, this, &QgsLayoutDesignerDialog::itemTypeAdded );
|
||
|
||
QToolButton *orderingToolButton = new QToolButton( this );
|
||
orderingToolButton->setPopupMode( QToolButton::InstantPopup );
|
||
orderingToolButton->setAutoRaise( true );
|
||
orderingToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly );
|
||
orderingToolButton->addAction( mActionRaiseItems );
|
||
orderingToolButton->addAction( mActionLowerItems );
|
||
orderingToolButton->addAction( mActionMoveItemsToTop );
|
||
orderingToolButton->addAction( mActionMoveItemsToBottom );
|
||
orderingToolButton->setDefaultAction( mActionRaiseItems );
|
||
mActionsToolbar->addWidget( orderingToolButton );
|
||
|
||
QToolButton *alignToolButton = new QToolButton( this );
|
||
alignToolButton->setPopupMode( QToolButton::InstantPopup );
|
||
alignToolButton->setAutoRaise( true );
|
||
alignToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly );
|
||
alignToolButton->addAction( mActionAlignLeft );
|
||
alignToolButton->addAction( mActionAlignHCenter );
|
||
alignToolButton->addAction( mActionAlignRight );
|
||
alignToolButton->addAction( mActionAlignTop );
|
||
alignToolButton->addAction( mActionAlignVCenter );
|
||
alignToolButton->addAction( mActionAlignBottom );
|
||
alignToolButton->setDefaultAction( mActionAlignLeft );
|
||
mActionsToolbar->addWidget( alignToolButton );
|
||
|
||
QToolButton *distributeToolButton = new QToolButton( this );
|
||
distributeToolButton->setPopupMode( QToolButton::InstantPopup );
|
||
distributeToolButton->setAutoRaise( true );
|
||
distributeToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly );
|
||
distributeToolButton->addAction( mActionDistributeLeft );
|
||
distributeToolButton->addAction( mActionDistributeHCenter );
|
||
distributeToolButton->addAction( mActionDistributeRight );
|
||
distributeToolButton->addAction( mActionDistributeTop );
|
||
distributeToolButton->addAction( mActionDistributeVCenter );
|
||
distributeToolButton->addAction( mActionDistributeBottom );
|
||
distributeToolButton->setDefaultAction( mActionDistributeLeft );
|
||
mActionsToolbar->addWidget( distributeToolButton );
|
||
|
||
QToolButton *resizeToolButton = new QToolButton( this );
|
||
resizeToolButton->setPopupMode( QToolButton::InstantPopup );
|
||
resizeToolButton->setAutoRaise( true );
|
||
resizeToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly );
|
||
resizeToolButton->addAction( mActionResizeNarrowest );
|
||
resizeToolButton->addAction( mActionResizeWidest );
|
||
resizeToolButton->addAction( mActionResizeShortest );
|
||
resizeToolButton->addAction( mActionResizeTallest );
|
||
resizeToolButton->addAction( mActionResizeToSquare );
|
||
resizeToolButton->setDefaultAction( mActionResizeNarrowest );
|
||
mActionsToolbar->addWidget( resizeToolButton );
|
||
|
||
QToolButton *atlasExportToolButton = new QToolButton( mAtlasToolbar );
|
||
atlasExportToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionExport.svg" ) ) );
|
||
atlasExportToolButton->setPopupMode( QToolButton::InstantPopup );
|
||
atlasExportToolButton->setAutoRaise( true );
|
||
atlasExportToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly );
|
||
atlasExportToolButton->addAction( mActionExportAtlasAsImage );
|
||
atlasExportToolButton->addAction( mActionExportAtlasAsSVG );
|
||
atlasExportToolButton->addAction( mActionExportAtlasAsPDF );
|
||
atlasExportToolButton->setToolTip( tr( "Export Atlas" ) );
|
||
mAtlasToolbar->insertWidget( mActionAtlasSettings, atlasExportToolButton );
|
||
mAtlasPageComboBox = new QComboBox();
|
||
mAtlasPageComboBox->setEditable( true );
|
||
mAtlasPageComboBox->addItem( QString::number( 1 ) );
|
||
mAtlasPageComboBox->setCurrentIndex( 0 );
|
||
mAtlasPageComboBox->setMinimumHeight( mAtlasToolbar->height() );
|
||
mAtlasPageComboBox->setMinimumContentsLength( 6 );
|
||
mAtlasPageComboBox->setMaxVisibleItems( 20 );
|
||
mAtlasPageComboBox->setSizeAdjustPolicy( QComboBox::AdjustToContents );
|
||
mAtlasPageComboBox->setInsertPolicy( QComboBox::NoInsert );
|
||
connect( mAtlasPageComboBox->lineEdit(), &QLineEdit::editingFinished, this, &QgsLayoutDesignerDialog::atlasPageComboEditingFinished );
|
||
connect( mAtlasPageComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutDesignerDialog::atlasPageComboEditingFinished );
|
||
mAtlasToolbar->insertWidget( mActionAtlasNext, mAtlasPageComboBox );
|
||
|
||
mAddItemTool = new QgsLayoutViewToolAddItem( mView );
|
||
mAddNodeItemTool = new QgsLayoutViewToolAddNodeItem( mView );
|
||
mPanTool = new QgsLayoutViewToolPan( mView );
|
||
mPanTool->setAction( mActionPan );
|
||
mToolsActionGroup->addAction( mActionPan );
|
||
connect( mActionPan, &QAction::triggered, mPanTool, [ = ] { mView->setTool( mPanTool ); } );
|
||
mZoomTool = new QgsLayoutViewToolZoom( mView );
|
||
mZoomTool->setAction( mActionZoomTool );
|
||
mToolsActionGroup->addAction( mActionZoomTool );
|
||
connect( mActionZoomTool, &QAction::triggered, mZoomTool, [ = ] { mView->setTool( mZoomTool ); } );
|
||
mSelectTool = new QgsLayoutViewToolSelect( mView );
|
||
mSelectTool->setAction( mActionSelectMoveItem );
|
||
mToolsActionGroup->addAction( mActionSelectMoveItem );
|
||
connect( mActionSelectMoveItem, &QAction::triggered, mSelectTool, [ = ] { mView->setTool( mSelectTool ); } );
|
||
// after creating an item with the add item tool, switch immediately to select tool
|
||
connect( mAddItemTool, &QgsLayoutViewToolAddItem::createdItem, this, [ = ] { mView->setTool( mSelectTool ); } );
|
||
connect( mAddNodeItemTool, &QgsLayoutViewToolAddNodeItem::createdItem, this, [ = ] { mView->setTool( mSelectTool ); } );
|
||
|
||
mNodesTool = new QgsLayoutViewToolEditNodes( mView );
|
||
mNodesTool->setAction( mActionEditNodesItem );
|
||
mToolsActionGroup->addAction( mActionEditNodesItem );
|
||
connect( mActionEditNodesItem, &QAction::triggered, mNodesTool, [ = ] { mView->setTool( mNodesTool ); } );
|
||
|
||
mMoveContentTool = new QgsLayoutViewToolMoveItemContent( mView );
|
||
mMoveContentTool->setAction( mActionMoveItemContent );
|
||
mToolsActionGroup->addAction( mActionMoveItemContent );
|
||
connect( mActionMoveItemContent, &QAction::triggered, mMoveContentTool, [ = ] { mView->setTool( mMoveContentTool ); } );
|
||
|
||
//Ctrl+= should also trigger zoom in
|
||
QShortcut *ctrlEquals = new QShortcut( QKeySequence( QStringLiteral( "Ctrl+=" ) ), this );
|
||
connect( ctrlEquals, &QShortcut::activated, mActionZoomIn, &QAction::trigger );
|
||
//Backspace should also trigger delete selection
|
||
QShortcut *backSpace = new QShortcut( QKeySequence( QStringLiteral( "Backspace" ) ), this );
|
||
connect( backSpace, &QShortcut::activated, mActionDeleteSelection, &QAction::trigger );
|
||
|
||
#ifdef Q_OS_MAC
|
||
// OSX has issues with QShortcut when certain children are focused
|
||
ctrlEquals->setParent( mView );
|
||
ctrlEquals->setContext( Qt::WidgetWithChildrenShortcut );
|
||
backSpace->setParent( mView );
|
||
backSpace->setContext( Qt::WidgetWithChildrenShortcut );
|
||
#endif
|
||
|
||
mActionPreviewModeOff->setChecked( true );
|
||
connect( mActionPreviewModeOff, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->setPreviewModeEnabled( false );
|
||
} );
|
||
connect( mActionPreviewModeGrayscale, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->setPreviewMode( QgsPreviewEffect::PreviewGrayscale );
|
||
mView->setPreviewModeEnabled( true );
|
||
} );
|
||
connect( mActionPreviewModeMono, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->setPreviewMode( QgsPreviewEffect::PreviewMono );
|
||
mView->setPreviewModeEnabled( true );
|
||
} );
|
||
connect( mActionPreviewProtanope, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->setPreviewMode( QgsPreviewEffect::PreviewProtanope );
|
||
mView->setPreviewModeEnabled( true );
|
||
} );
|
||
connect( mActionPreviewDeuteranope, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->setPreviewMode( QgsPreviewEffect::PreviewDeuteranope );
|
||
mView->setPreviewModeEnabled( true );
|
||
} );
|
||
QActionGroup *previewGroup = new QActionGroup( this );
|
||
previewGroup->setExclusive( true );
|
||
mActionPreviewModeOff->setActionGroup( previewGroup );
|
||
mActionPreviewModeGrayscale->setActionGroup( previewGroup );
|
||
mActionPreviewModeMono->setActionGroup( previewGroup );
|
||
mActionPreviewProtanope->setActionGroup( previewGroup );
|
||
mActionPreviewDeuteranope->setActionGroup( previewGroup );
|
||
|
||
connect( mActionSaveAsTemplate, &QAction::triggered, this, &QgsLayoutDesignerDialog::saveAsTemplate );
|
||
connect( mActionLoadFromTemplate, &QAction::triggered, this, &QgsLayoutDesignerDialog::addItemsFromTemplate );
|
||
connect( mActionDuplicateLayout, &QAction::triggered, this, &QgsLayoutDesignerDialog::duplicate );
|
||
connect( mActionRenameLayout, &QAction::triggered, this, &QgsLayoutDesignerDialog::renameLayout );
|
||
|
||
connect( mActionZoomIn, &QAction::triggered, mView, &QgsLayoutView::zoomIn );
|
||
connect( mActionZoomOut, &QAction::triggered, mView, &QgsLayoutView::zoomOut );
|
||
connect( mActionZoomAll, &QAction::triggered, mView, &QgsLayoutView::zoomFull );
|
||
connect( mActionZoomActual, &QAction::triggered, mView, &QgsLayoutView::zoomActual );
|
||
connect( mActionZoomToWidth, &QAction::triggered, mView, &QgsLayoutView::zoomWidth );
|
||
|
||
connect( mActionSelectAll, &QAction::triggered, mView, &QgsLayoutView::selectAll );
|
||
connect( mActionDeselectAll, &QAction::triggered, mView, &QgsLayoutView::deselectAll );
|
||
connect( mActionInvertSelection, &QAction::triggered, mView, &QgsLayoutView::invertSelection );
|
||
connect( mActionSelectNextAbove, &QAction::triggered, mView, &QgsLayoutView::selectNextItemAbove );
|
||
connect( mActionSelectNextBelow, &QAction::triggered, mView, &QgsLayoutView::selectNextItemBelow );
|
||
|
||
connect( mActionRaiseItems, &QAction::triggered, this, &QgsLayoutDesignerDialog::raiseSelectedItems );
|
||
connect( mActionLowerItems, &QAction::triggered, this, &QgsLayoutDesignerDialog::lowerSelectedItems );
|
||
connect( mActionMoveItemsToTop, &QAction::triggered, this, &QgsLayoutDesignerDialog::moveSelectedItemsToTop );
|
||
connect( mActionMoveItemsToBottom, &QAction::triggered, this, &QgsLayoutDesignerDialog::moveSelectedItemsToBottom );
|
||
connect( mActionAlignLeft, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->alignSelectedItems( QgsLayoutAligner::AlignLeft );
|
||
} );
|
||
connect( mActionAlignHCenter, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->alignSelectedItems( QgsLayoutAligner::AlignHCenter );
|
||
} );
|
||
connect( mActionAlignRight, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->alignSelectedItems( QgsLayoutAligner::AlignRight );
|
||
} );
|
||
connect( mActionAlignTop, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->alignSelectedItems( QgsLayoutAligner::AlignTop );
|
||
} );
|
||
connect( mActionAlignVCenter, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->alignSelectedItems( QgsLayoutAligner::AlignVCenter );
|
||
} );
|
||
connect( mActionAlignBottom, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->alignSelectedItems( QgsLayoutAligner::AlignBottom );
|
||
} );
|
||
connect( mActionDistributeLeft, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->distributeSelectedItems( QgsLayoutAligner::DistributeLeft );
|
||
} );
|
||
connect( mActionDistributeHCenter, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->distributeSelectedItems( QgsLayoutAligner::DistributeHCenter );
|
||
} );
|
||
connect( mActionDistributeRight, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->distributeSelectedItems( QgsLayoutAligner::DistributeRight );
|
||
} );
|
||
connect( mActionDistributeTop, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->distributeSelectedItems( QgsLayoutAligner::DistributeTop );
|
||
} );
|
||
connect( mActionDistributeVCenter, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->distributeSelectedItems( QgsLayoutAligner::DistributeVCenter );
|
||
} );
|
||
connect( mActionDistributeBottom, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->distributeSelectedItems( QgsLayoutAligner::DistributeBottom );
|
||
} );
|
||
connect( mActionResizeNarrowest, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->resizeSelectedItems( QgsLayoutAligner::ResizeNarrowest );
|
||
} );
|
||
connect( mActionResizeWidest, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->resizeSelectedItems( QgsLayoutAligner::ResizeWidest );
|
||
} );
|
||
connect( mActionResizeShortest, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->resizeSelectedItems( QgsLayoutAligner::ResizeShortest );
|
||
} );
|
||
connect( mActionResizeTallest, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->resizeSelectedItems( QgsLayoutAligner::ResizeTallest );
|
||
} );
|
||
connect( mActionResizeToSquare, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->resizeSelectedItems( QgsLayoutAligner::ResizeToSquare );
|
||
} );
|
||
|
||
connect( mActionAddPages, &QAction::triggered, this, &QgsLayoutDesignerDialog::addPages );
|
||
|
||
connect( mActionUnlockAll, &QAction::triggered, this, &QgsLayoutDesignerDialog::unlockAllItems );
|
||
connect( mActionLockItems, &QAction::triggered, this, &QgsLayoutDesignerDialog::lockSelectedItems );
|
||
|
||
connect( mActionHidePanels, &QAction::toggled, this, &QgsLayoutDesignerDialog::setPanelVisibility );
|
||
|
||
connect( mActionDeleteSelection, &QAction::triggered, this, [ = ]
|
||
{
|
||
if ( mView->tool() == mNodesTool )
|
||
mNodesTool->deleteSelectedNode();
|
||
else
|
||
mView->deleteSelectedItems();
|
||
} );
|
||
connect( mActionGroupItems, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->groupSelectedItems();
|
||
} );
|
||
connect( mActionUngroupItems, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->ungroupSelectedItems();
|
||
} );
|
||
|
||
//cut/copy/paste actions. Note these are not included in the ui file
|
||
//as ui files have no support for QKeySequence shortcuts
|
||
mActionCut = new QAction( tr( "Cu&t" ), this );
|
||
mActionCut->setShortcuts( QKeySequence::Cut );
|
||
mActionCut->setStatusTip( tr( "Cut" ) );
|
||
mActionCut->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCut.svg" ) ) );
|
||
connect( mActionCut, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->copySelectedItems( QgsLayoutView::ClipboardCut );
|
||
} );
|
||
|
||
mActionCopy = new QAction( tr( "&Copy" ), this );
|
||
mActionCopy->setShortcuts( QKeySequence::Copy );
|
||
mActionCopy->setStatusTip( tr( "Copy" ) );
|
||
mActionCopy->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditCopy.svg" ) ) );
|
||
connect( mActionCopy, &QAction::triggered, this, [ = ]
|
||
{
|
||
mView->copySelectedItems( QgsLayoutView::ClipboardCopy );
|
||
} );
|
||
|
||
mActionPaste = new QAction( tr( "&Paste" ), this );
|
||
mActionPaste->setShortcuts( QKeySequence::Paste );
|
||
mActionPaste->setStatusTip( tr( "Paste" ) );
|
||
mActionPaste->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditPaste.svg" ) ) );
|
||
connect( mActionPaste, &QAction::triggered, this, &QgsLayoutDesignerDialog::paste );
|
||
|
||
menuEdit->insertAction( mActionPasteInPlace, mActionCut );
|
||
menuEdit->insertAction( mActionPasteInPlace, mActionCopy );
|
||
menuEdit->insertAction( mActionPasteInPlace, mActionPaste );
|
||
|
||
//create status bar labels
|
||
mStatusCursorXLabel = new QLabel( mStatusBar );
|
||
mStatusCursorXLabel->setMinimumWidth( 100 );
|
||
mStatusCursorYLabel = new QLabel( mStatusBar );
|
||
mStatusCursorYLabel->setMinimumWidth( 100 );
|
||
mStatusCursorPageLabel = new QLabel( mStatusBar );
|
||
mStatusCursorPageLabel->setMinimumWidth( 100 );
|
||
|
||
mStatusBar->addPermanentWidget( mStatusCursorXLabel );
|
||
mStatusBar->addPermanentWidget( mStatusCursorXLabel );
|
||
mStatusBar->addPermanentWidget( mStatusCursorYLabel );
|
||
mStatusBar->addPermanentWidget( mStatusCursorPageLabel );
|
||
|
||
mStatusZoomCombo = new QComboBox();
|
||
mStatusZoomCombo->setEditable( true );
|
||
mStatusZoomCombo->setInsertPolicy( QComboBox::NoInsert );
|
||
mStatusZoomCombo->setCompleter( nullptr );
|
||
mStatusZoomCombo->setMinimumWidth( 100 );
|
||
//zoom combo box accepts decimals in the range 1-9999, with an optional decimal point and "%" sign
|
||
QRegularExpression zoomRx( QStringLiteral( "\\s*\\d{1,4}(\\.\\d?)?\\s*%?" ) );
|
||
QValidator *zoomValidator = new QRegularExpressionValidator( zoomRx, mStatusZoomCombo );
|
||
mStatusZoomCombo->lineEdit()->setValidator( zoomValidator );
|
||
|
||
Q_FOREACH ( double level, sStatusZoomLevelsList )
|
||
{
|
||
mStatusZoomCombo->insertItem( 0, tr( "%1%" ).arg( level * 100.0, 0, 'f', 1 ), level );
|
||
}
|
||
mStatusZoomCombo->insertItem( 0, tr( "Fit Layout" ), FIT_LAYOUT );
|
||
mStatusZoomCombo->insertItem( 0, tr( "Fit Layout Width" ), FIT_LAYOUT_WIDTH );
|
||
connect( mStatusZoomCombo, static_cast<void ( QComboBox::* )( int )>( &QComboBox::activated ), this, &QgsLayoutDesignerDialog::statusZoomCombo_currentIndexChanged );
|
||
connect( mStatusZoomCombo->lineEdit(), &QLineEdit::returnPressed, this, &QgsLayoutDesignerDialog::statusZoomCombo_zoomEntered );
|
||
|
||
mStatusZoomSlider = new QSlider();
|
||
mStatusZoomSlider->setFixedWidth( mStatusZoomCombo->width() );
|
||
mStatusZoomSlider->setOrientation( Qt::Horizontal );
|
||
mStatusZoomSlider->setMinimum( 20 );
|
||
mStatusZoomSlider->setMaximum( 800 );
|
||
connect( mStatusZoomSlider, &QSlider::valueChanged, this, &QgsLayoutDesignerDialog::sliderZoomChanged );
|
||
|
||
mStatusZoomCombo->setToolTip( tr( "Zoom level" ) );
|
||
mStatusZoomSlider->setToolTip( tr( "Zoom level" ) );
|
||
|
||
mStatusBar->addPermanentWidget( mStatusZoomCombo );
|
||
mStatusBar->addPermanentWidget( mStatusZoomSlider );
|
||
|
||
//hide borders from child items in status bar under Windows
|
||
mStatusBar->setStyleSheet( QStringLiteral( "QStatusBar::item {border: none;}" ) );
|
||
|
||
mView->setTool( mSelectTool );
|
||
mView->setFocus();
|
||
connect( mView, &QgsLayoutView::zoomLevelChanged, this, &QgsLayoutDesignerDialog::updateStatusZoom );
|
||
connect( mView, &QgsLayoutView::cursorPosChanged, this, &QgsLayoutDesignerDialog::updateStatusCursorPos );
|
||
//also listen out for position updates from the horizontal/vertical rulers
|
||
connect( mHorizontalRuler, &QgsLayoutRuler::cursorPosChanged, this, &QgsLayoutDesignerDialog::updateStatusCursorPos );
|
||
connect( mVerticalRuler, &QgsLayoutRuler::cursorPosChanged, this, &QgsLayoutDesignerDialog::updateStatusCursorPos );
|
||
|
||
connect( mView, &QgsLayoutView::itemFocused, this, [ = ]( QgsLayoutItem * item )
|
||
{
|
||
showItemOptions( item, false );
|
||
} );
|
||
|
||
// Panel and toolbar submenus
|
||
mToolbarMenu->addAction( mLayoutToolbar->toggleViewAction() );
|
||
mToolbarMenu->addAction( mNavigationToolbar->toggleViewAction() );
|
||
mToolbarMenu->addAction( mToolsToolbar->toggleViewAction() );
|
||
mToolbarMenu->addAction( mActionsToolbar->toggleViewAction() );
|
||
mToolbarMenu->addAction( mAtlasToolbar->toggleViewAction() );
|
||
mToolbarMenu->addAction( mReportToolbar->toggleViewAction() );
|
||
|
||
connect( mActionToggleFullScreen, &QAction::toggled, this, &QgsLayoutDesignerDialog::toggleFullScreen );
|
||
|
||
mMenuProvider = new QgsLayoutAppMenuProvider( this );
|
||
mView->setMenuProvider( mMenuProvider );
|
||
|
||
int minDockWidth( fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) ) );
|
||
|
||
setTabPosition( Qt::AllDockWidgetAreas, QTabWidget::North );
|
||
mGeneralDock = new QgsDockWidget( tr( "Layout" ), this );
|
||
mGeneralDock->setObjectName( QStringLiteral( "LayoutDock" ) );
|
||
mGeneralDock->setMinimumWidth( minDockWidth );
|
||
mGeneralPropertiesStack = new QgsPanelWidgetStack();
|
||
mGeneralDock->setWidget( mGeneralPropertiesStack );
|
||
mPanelsMenu->addAction( mGeneralDock->toggleViewAction() );
|
||
connect( mActionLayoutProperties, &QAction::triggered, this, [ = ]
|
||
{
|
||
mGeneralDock->setUserVisible( true );
|
||
} );
|
||
|
||
mItemDock = new QgsDockWidget( tr( "Item Properties" ), this );
|
||
mItemDock->setObjectName( QStringLiteral( "ItemDock" ) );
|
||
mItemDock->setMinimumWidth( minDockWidth );
|
||
mItemPropertiesStack = new QgsPanelWidgetStack();
|
||
mItemDock->setWidget( mItemPropertiesStack );
|
||
mPanelsMenu->addAction( mItemDock->toggleViewAction() );
|
||
|
||
mGuideDock = new QgsDockWidget( tr( "Guides" ), this );
|
||
mGuideDock->setObjectName( QStringLiteral( "GuideDock" ) );
|
||
mGuideDock->setMinimumWidth( minDockWidth );
|
||
mGuideStack = new QgsPanelWidgetStack();
|
||
mGuideDock->setWidget( mGuideStack );
|
||
mPanelsMenu->addAction( mGuideDock->toggleViewAction() );
|
||
connect( mActionManageGuides, &QAction::triggered, this, [ = ]
|
||
{
|
||
mGuideDock->setUserVisible( true );
|
||
} );
|
||
|
||
mUndoDock = new QgsDockWidget( tr( "Undo History" ), this );
|
||
mUndoDock->setObjectName( QStringLiteral( "UndoDock" ) );
|
||
mPanelsMenu->addAction( mUndoDock->toggleViewAction() );
|
||
mUndoView = new QUndoView( this );
|
||
mUndoDock->setWidget( mUndoView );
|
||
|
||
mItemsDock = new QgsDockWidget( tr( "Items" ), this );
|
||
mItemsDock->setObjectName( QStringLiteral( "ItemsDock" ) );
|
||
mPanelsMenu->addAction( mItemsDock->toggleViewAction() );
|
||
|
||
//items tree widget
|
||
mItemsTreeView = new QgsLayoutItemsListView( mItemsDock, this );
|
||
mItemsDock->setWidget( mItemsTreeView );
|
||
|
||
mAtlasDock = new QgsDockWidget( tr( "Atlas" ), this );
|
||
mAtlasDock->setObjectName( QStringLiteral( "AtlasDock" ) );
|
||
mAtlasDock->setToggleVisibilityAction( mActionAtlasSettings );
|
||
|
||
mReportDock = new QgsDockWidget( tr( "Report Organizer" ), this );
|
||
mReportDock->setObjectName( QStringLiteral( "ReportDock" ) );
|
||
mReportDock->setToggleVisibilityAction( mActionReportSettings );
|
||
|
||
const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
|
||
for ( QDockWidget *dock : docks )
|
||
{
|
||
connect( dock, &QDockWidget::visibilityChanged, this, &QgsLayoutDesignerDialog::dockVisibilityChanged );
|
||
}
|
||
|
||
addDockWidget( Qt::RightDockWidgetArea, mItemDock );
|
||
addDockWidget( Qt::RightDockWidgetArea, mGeneralDock );
|
||
addDockWidget( Qt::RightDockWidgetArea, mGuideDock );
|
||
addDockWidget( Qt::RightDockWidgetArea, mUndoDock );
|
||
addDockWidget( Qt::RightDockWidgetArea, mItemsDock );
|
||
addDockWidget( Qt::RightDockWidgetArea, mAtlasDock );
|
||
addDockWidget( Qt::LeftDockWidgetArea, mReportDock );
|
||
|
||
createLayoutPropertiesWidget();
|
||
|
||
mUndoDock->show();
|
||
mItemDock->show();
|
||
mGeneralDock->show();
|
||
mAtlasDock->show();
|
||
mReportDock->show();
|
||
mItemsDock->show();
|
||
|
||
tabifyDockWidget( mGeneralDock, mUndoDock );
|
||
tabifyDockWidget( mItemDock, mUndoDock );
|
||
tabifyDockWidget( mGeneralDock, mItemDock );
|
||
tabifyDockWidget( mItemDock, mItemsDock );
|
||
tabifyDockWidget( mItemDock, mAtlasDock );
|
||
|
||
toggleActions( false );
|
||
|
||
//set initial state of atlas controls
|
||
mActionAtlasPreview->setEnabled( false );
|
||
mActionAtlasPreview->setChecked( false );
|
||
mActionAtlasFirst->setEnabled( false );
|
||
mActionAtlasLast->setEnabled( false );
|
||
mActionAtlasNext->setEnabled( false );
|
||
mActionAtlasPrev->setEnabled( false );
|
||
mActionPrintAtlas->setEnabled( false );
|
||
mAtlasPageComboBox->setEnabled( false );
|
||
mActionExportAtlasAsImage->setEnabled( false );
|
||
mActionExportAtlasAsSVG->setEnabled( false );
|
||
mActionExportAtlasAsPDF->setEnabled( false );
|
||
|
||
mLayoutsMenu->setObjectName( QStringLiteral( "mLayoutsMenu" ) );
|
||
connect( mLayoutsMenu, &QMenu::aboutToShow, this, &QgsLayoutDesignerDialog::populateLayoutsMenu );
|
||
|
||
QList<QAction *> actions = mPanelsMenu->actions();
|
||
std::sort( actions.begin(), actions.end(), cmpByText_ );
|
||
mPanelsMenu->insertActions( nullptr, actions );
|
||
|
||
actions = mToolbarMenu->actions();
|
||
std::sort( actions.begin(), actions.end(), cmpByText_ );
|
||
mToolbarMenu->insertActions( nullptr, actions );
|
||
|
||
restoreWindowState();
|
||
|
||
//listen out to status bar updates from the view
|
||
connect( mView, &QgsLayoutView::statusMessage, this, &QgsLayoutDesignerDialog::statusMessageReceived );
|
||
|
||
connect( QgsProject::instance(), &QgsProject::isDirtyChanged, this, &QgsLayoutDesignerDialog::updateWindowTitle );
|
||
}
|
||
|
||
QgsAppLayoutDesignerInterface *QgsLayoutDesignerDialog::iface()
|
||
{
|
||
return mInterface;
|
||
}
|
||
|
||
QMenu *QgsLayoutDesignerDialog::createPopupMenu()
|
||
{
|
||
QMenu *menu = QMainWindow::createPopupMenu();
|
||
QList< QAction * > al = menu->actions();
|
||
QList< QAction * > panels, toolbars;
|
||
|
||
if ( !al.isEmpty() )
|
||
{
|
||
bool found = false;
|
||
for ( int i = 0; i < al.size(); ++i )
|
||
{
|
||
if ( al[ i ]->isSeparator() )
|
||
{
|
||
found = true;
|
||
continue;
|
||
}
|
||
|
||
if ( !found )
|
||
{
|
||
panels.append( al[ i ] );
|
||
}
|
||
else
|
||
{
|
||
toolbars.append( al[ i ] );
|
||
}
|
||
}
|
||
|
||
std::sort( panels.begin(), panels.end(), cmpByText_ );
|
||
QWidgetAction *panelstitle = new QWidgetAction( menu );
|
||
QLabel *plabel = new QLabel( QStringLiteral( "<b>%1</b>" ).arg( tr( "Panels" ) ) );
|
||
plabel->setMargin( 3 );
|
||
plabel->setAlignment( Qt::AlignHCenter );
|
||
panelstitle->setDefaultWidget( plabel );
|
||
menu->addAction( panelstitle );
|
||
Q_FOREACH ( QAction *a, panels )
|
||
{
|
||
if ( ( a == mAtlasDock->toggleViewAction() && !dynamic_cast< QgsPrintLayout * >( mMasterLayout ) ) ||
|
||
( a == mReportDock->toggleViewAction() && !dynamic_cast< QgsReport * >( mMasterLayout ) ) )
|
||
{
|
||
a->setVisible( false );
|
||
}
|
||
|
||
if ( !a->property( "fixed_title" ).toBool() )
|
||
{
|
||
// append " Panel" to menu text. Only ever do this once, because the actions are not unique to
|
||
// this single popup menu
|
||
a->setText( tr( "%1 Panel" ).arg( a->text() ) );
|
||
a->setProperty( "fixed_title", true );
|
||
}
|
||
|
||
menu->addAction( a );
|
||
}
|
||
menu->addSeparator();
|
||
QWidgetAction *toolbarstitle = new QWidgetAction( menu );
|
||
QLabel *tlabel = new QLabel( QStringLiteral( "<b>%1</b>" ).arg( tr( "Toolbars" ) ) );
|
||
tlabel->setMargin( 3 );
|
||
tlabel->setAlignment( Qt::AlignHCenter );
|
||
toolbarstitle->setDefaultWidget( tlabel );
|
||
menu->addAction( toolbarstitle );
|
||
std::sort( toolbars.begin(), toolbars.end(), cmpByText_ );
|
||
Q_FOREACH ( QAction *a, toolbars )
|
||
{
|
||
if ( ( a == mAtlasToolbar->toggleViewAction() && !dynamic_cast< QgsPrintLayout * >( mMasterLayout ) ) ||
|
||
( a == mReportToolbar->toggleViewAction() && !dynamic_cast< QgsReport * >( mMasterLayout ) ) )
|
||
{
|
||
a->setVisible( false );
|
||
}
|
||
menu->addAction( a );
|
||
}
|
||
}
|
||
|
||
return menu;
|
||
}
|
||
|
||
QgsLayout *QgsLayoutDesignerDialog::currentLayout()
|
||
{
|
||
return mLayout;
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::setMasterLayout( QgsMasterLayoutInterface *layout )
|
||
{
|
||
mMasterLayout = layout;
|
||
|
||
QObject *obj = dynamic_cast< QObject * >( mMasterLayout );
|
||
if ( obj )
|
||
connect( obj, &QObject::destroyed, this, &QgsLayoutDesignerDialog::close );
|
||
|
||
setTitle( mMasterLayout->name() );
|
||
|
||
if ( QgsPrintLayout *l = dynamic_cast< QgsPrintLayout * >( layout ) )
|
||
{
|
||
connect( l, &QgsPrintLayout::nameChanged, this, &QgsLayoutDesignerDialog::setTitle );
|
||
setCurrentLayout( l );
|
||
}
|
||
else if ( QgsReport *r = dynamic_cast< QgsReport * >( layout ) )
|
||
{
|
||
connect( r, &QgsReport::nameChanged, this, &QgsLayoutDesignerDialog::setTitle );
|
||
}
|
||
|
||
if ( dynamic_cast< QgsPrintLayout * >( layout ) )
|
||
{
|
||
createAtlasWidget();
|
||
}
|
||
else
|
||
{
|
||
// ideally we'd only create mAtlasDock in createAtlasWidget() -
|
||
// but if we do that, then it's always brought to the focus
|
||
// in tab widgets
|
||
mAtlasDock->hide();
|
||
mPanelsMenu->removeAction( mAtlasDock->toggleViewAction() );
|
||
delete mMenuAtlas;
|
||
mMenuAtlas = nullptr;
|
||
mAtlasToolbar->hide();
|
||
mToolbarMenu->removeAction( mAtlasToolbar->toggleViewAction() );
|
||
}
|
||
|
||
if ( dynamic_cast< QgsReport * >( layout ) )
|
||
{
|
||
createReportWidget();
|
||
}
|
||
else
|
||
{
|
||
// ideally we'd only create mReportDock in createReportWidget() -
|
||
// but if we do that, then it's always brought to the focus
|
||
// in tab widgets
|
||
mReportDock->hide();
|
||
mPanelsMenu->removeAction( mReportDock->toggleViewAction() );
|
||
delete mMenuReport;
|
||
mMenuReport = nullptr;
|
||
mReportToolbar->hide();
|
||
mToolbarMenu->removeAction( mReportToolbar->toggleViewAction() );
|
||
}
|
||
|
||
updateActionNames( mMasterLayout->layoutType() );
|
||
}
|
||
|
||
QgsMasterLayoutInterface *QgsLayoutDesignerDialog::masterLayout()
|
||
{
|
||
return mMasterLayout;
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::setCurrentLayout( QgsLayout *layout )
|
||
{
|
||
if ( !layout )
|
||
{
|
||
toggleActions( false );
|
||
}
|
||
else
|
||
{
|
||
layout->deselectAll();
|
||
mLayout = layout;
|
||
|
||
mView->setCurrentLayout( layout );
|
||
|
||
// add undo/redo actions which apply to the correct layout undo stack
|
||
delete mUndoAction;
|
||
delete mRedoAction;
|
||
mUndoAction = layout->undoStack()->stack()->createUndoAction( this );
|
||
mUndoAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionUndo.svg" ) ) );
|
||
mUndoAction->setShortcuts( QKeySequence::Undo );
|
||
mRedoAction = layout->undoStack()->stack()->createRedoAction( this );
|
||
mRedoAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRedo.svg" ) ) );
|
||
mRedoAction->setShortcuts( QKeySequence::Redo );
|
||
menuEdit->insertAction( menuEdit->actions().at( 0 ), mRedoAction );
|
||
menuEdit->insertAction( mRedoAction, mUndoAction );
|
||
mLayoutToolbar->addAction( mUndoAction );
|
||
mLayoutToolbar->addAction( mRedoAction );
|
||
|
||
connect( mLayout->undoStack(), &QgsLayoutUndoStack::undoRedoOccurredForItems, this, &QgsLayoutDesignerDialog::undoRedoOccurredForItems );
|
||
connect( mActionClearGuides, &QAction::triggered, &mLayout->guides(), [ = ]
|
||
{
|
||
mLayout->guides().clear();
|
||
} );
|
||
|
||
mActionShowGrid->setChecked( mLayout->renderContext().gridVisible() );
|
||
mActionSnapGrid->setChecked( mLayout->snapper().snapToGrid() );
|
||
mActionShowGuides->setChecked( mLayout->guides().visible() );
|
||
mActionSnapGuides->setChecked( mLayout->snapper().snapToGuides() );
|
||
mActionSmartGuides->setChecked( mLayout->snapper().snapToItems() );
|
||
mActionShowBoxes->setChecked( mLayout->renderContext().boundingBoxesVisible() );
|
||
mActionShowPage->setChecked( mLayout->renderContext().pagesVisible() );
|
||
|
||
mUndoView->setStack( mLayout->undoStack()->stack() );
|
||
|
||
mSelectTool->setLayout( layout );
|
||
mItemsTreeView->setCurrentLayout( mLayout );
|
||
#ifdef ENABLE_MODELTEST
|
||
new ModelTest( mLayout->itemsModel(), this );
|
||
#endif
|
||
|
||
createLayoutPropertiesWidget();
|
||
toggleActions( true );
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::setIconSizes( int size )
|
||
{
|
||
//Set the icon size of for all the toolbars created in the future.
|
||
setIconSize( QSize( size, size ) );
|
||
|
||
//Change all current icon sizes.
|
||
QList<QToolBar *> toolbars = findChildren<QToolBar *>();
|
||
Q_FOREACH ( QToolBar *toolbar, toolbars )
|
||
{
|
||
toolbar->setIconSize( QSize( size, size ) );
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::showItemOptions( QgsLayoutItem *item, bool bringPanelToFront )
|
||
{
|
||
if ( mBlockItemOptions )
|
||
return;
|
||
|
||
if ( !item )
|
||
{
|
||
delete mItemPropertiesStack->takeMainPanel();
|
||
return;
|
||
}
|
||
|
||
if ( auto widget = qobject_cast< QgsLayoutItemBaseWidget * >( mItemPropertiesStack->mainPanel() ) )
|
||
{
|
||
if ( widget->layoutObject() == item )
|
||
{
|
||
// already showing properties for this item - we don't want to create a new panel
|
||
if ( bringPanelToFront )
|
||
mItemDock->setUserVisible( true );
|
||
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
// try to reuse
|
||
if ( widget->setItem( item ) )
|
||
{
|
||
if ( bringPanelToFront )
|
||
mItemDock->setUserVisible( true );
|
||
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
std::unique_ptr< QgsLayoutItemBaseWidget > widget( QgsGui::layoutItemGuiRegistry()->createItemWidget( item ) );
|
||
delete mItemPropertiesStack->takeMainPanel();
|
||
|
||
if ( ! widget )
|
||
return;
|
||
|
||
widget->setReportTypeString( reportTypeString() );
|
||
|
||
if ( QgsLayoutPagePropertiesWidget *ppWidget = qobject_cast< QgsLayoutPagePropertiesWidget * >( widget.get() ) )
|
||
connect( ppWidget, &QgsLayoutPagePropertiesWidget::pageOrientationChanged, this, &QgsLayoutDesignerDialog::pageOrientationChanged );
|
||
|
||
widget->setDockMode( true );
|
||
connect( item, &QgsLayoutItem::destroyed, widget.get(), [this]
|
||
{
|
||
delete mItemPropertiesStack->takeMainPanel();
|
||
} );
|
||
|
||
mItemPropertiesStack->setMainPanel( widget.release() );
|
||
if ( bringPanelToFront )
|
||
mItemDock->setUserVisible( true );
|
||
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::open()
|
||
{
|
||
show();
|
||
activate();
|
||
if ( mView )
|
||
{
|
||
mView->zoomFull(); // zoomFull() does not work properly until we have called show()
|
||
}
|
||
|
||
if ( mMasterLayout && mMasterLayout->layoutType() == QgsMasterLayoutInterface::Report )
|
||
{
|
||
mReportDock->show();
|
||
mReportDock->raise();
|
||
mReportDock->setUserVisible( true );
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::activate()
|
||
{
|
||
// bool shown = isVisible();
|
||
show();
|
||
raise();
|
||
setWindowState( windowState() & ~Qt::WindowMinimized );
|
||
activateWindow();
|
||
|
||
#if 0 // TODO
|
||
if ( !shown )
|
||
{
|
||
on_mActionZoomAll_triggered();
|
||
}
|
||
#endif
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::showRulers( bool visible )
|
||
{
|
||
//show or hide rulers
|
||
mHorizontalRuler->setVisible( visible );
|
||
mVerticalRuler->setVisible( visible );
|
||
mRulerLayoutFix->setVisible( visible );
|
||
|
||
QgsSettings settings;
|
||
settings.setValue( QStringLiteral( "LayoutDesigner/showRulers" ), visible, QgsSettings::App );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::showGrid( bool visible )
|
||
{
|
||
mLayout->renderContext().setGridVisible( visible );
|
||
mLayout->pageCollection()->redraw();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::showBoxes( bool visible )
|
||
{
|
||
mLayout->renderContext().setBoundingBoxesVisible( visible );
|
||
mSelectTool->mouseHandles()->update();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::showPages( bool visible )
|
||
{
|
||
mLayout->renderContext().setPagesVisible( visible );
|
||
mLayout->pageCollection()->redraw();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::snapToGrid( bool enabled )
|
||
{
|
||
mLayout->snapper().setSnapToGrid( enabled );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::showGuides( bool visible )
|
||
{
|
||
mLayout->guides().setVisible( visible );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::snapToGuides( bool enabled )
|
||
{
|
||
mLayout->snapper().setSnapToGuides( enabled );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::snapToItems( bool enabled )
|
||
{
|
||
mLayout->snapper().setSnapToItems( enabled );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::unlockAllItems()
|
||
{
|
||
mView->unlockAllItems();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::lockSelectedItems()
|
||
{
|
||
mView->lockSelectedItems();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::setPanelVisibility( bool hidden )
|
||
{
|
||
/*
|
||
workaround the limited Qt dock widget API
|
||
see http://qt-project.org/forums/viewthread/1141/
|
||
and http://qt-project.org/faq/answer/how_can_i_check_which_tab_is_the_current_one_in_a_tabbed_qdockwidget
|
||
*/
|
||
|
||
const QList<QDockWidget *> docks = findChildren<QDockWidget *>();
|
||
const QList<QTabBar *> tabBars = findChildren<QTabBar *>();
|
||
|
||
if ( hidden )
|
||
{
|
||
mPanelStatus.clear();
|
||
//record status of all docks
|
||
|
||
for ( QDockWidget *dock : docks )
|
||
{
|
||
mPanelStatus.insert( dock->windowTitle(), PanelStatus( dock->isVisible(), false ) );
|
||
dock->setVisible( false );
|
||
}
|
||
|
||
//record active dock tabs
|
||
for ( QTabBar *tabBar : tabBars )
|
||
{
|
||
QString currentTabTitle = tabBar->tabText( tabBar->currentIndex() );
|
||
mPanelStatus[ currentTabTitle ].isActive = true;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//restore visibility of all docks
|
||
for ( QDockWidget *dock : docks )
|
||
{
|
||
if ( ! mPanelStatus.contains( dock->windowTitle() ) )
|
||
{
|
||
dock->setVisible( true );
|
||
continue;
|
||
}
|
||
dock->setVisible( mPanelStatus.value( dock->windowTitle() ).isVisible );
|
||
}
|
||
|
||
//restore previously active dock tabs
|
||
for ( QTabBar *tabBar : tabBars )
|
||
{
|
||
//loop through all tabs in tab bar
|
||
for ( int i = 0; i < tabBar->count(); ++i )
|
||
{
|
||
QString tabTitle = tabBar->tabText( i );
|
||
if ( mPanelStatus.value( tabTitle ).isActive )
|
||
{
|
||
tabBar->setCurrentIndex( i );
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::raiseSelectedItems()
|
||
{
|
||
mView->raiseSelectedItems();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::lowerSelectedItems()
|
||
{
|
||
mView->lowerSelectedItems();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::moveSelectedItemsToTop()
|
||
{
|
||
mView->moveSelectedItemsToTop();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::moveSelectedItemsToBottom()
|
||
{
|
||
mView->moveSelectedItemsToBottom();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::refreshLayout()
|
||
{
|
||
if ( !currentLayout() )
|
||
{
|
||
return;
|
||
}
|
||
|
||
//refresh atlas feature first, to force an update of feature
|
||
//in case feature attributes or geometry has changed
|
||
if ( QgsLayoutAtlas *printAtlas = atlas() )
|
||
{
|
||
if ( printAtlas->enabled() && mActionAtlasPreview->isChecked() )
|
||
{
|
||
//block signals from atlas, since the later call to mComposition->refreshItems() will
|
||
//also trigger items to refresh atlas dependent properties
|
||
whileBlocking( printAtlas )->refreshCurrentFeature();
|
||
}
|
||
}
|
||
|
||
currentLayout()->refresh();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::closeEvent( QCloseEvent * )
|
||
{
|
||
emit aboutToClose();
|
||
saveWindowState();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::dropEvent( QDropEvent *event )
|
||
{
|
||
// dragging app is locked for the duration of dropEvent. This causes explorer windows to hang
|
||
// while large projects/layers are loaded. So instead we return from dropEvent as quickly as possible
|
||
// and do the actual handling of the drop after a very short timeout
|
||
QTimer *timer = new QTimer( this );
|
||
timer->setSingleShot( true );
|
||
timer->setInterval( 50 );
|
||
|
||
// get the file list
|
||
QList<QUrl>::iterator i;
|
||
QList<QUrl>urls = event->mimeData()->urls();
|
||
QStringList files;
|
||
for ( i = urls.begin(); i != urls.end(); ++i )
|
||
{
|
||
QString fileName = i->toLocalFile();
|
||
#ifdef Q_OS_MAC
|
||
// Mac OS X 10.10, under Qt4.8 ,changes dropped URL format
|
||
// https://bugreports.qt.io/browse/QTBUG-40449
|
||
// [pzion 20150805] Work around
|
||
if ( fileName.startsWith( "/.file/id=" ) )
|
||
{
|
||
QgsDebugMsg( QStringLiteral( "Mac dropped URL with /.file/id= (converting)" ) );
|
||
CFStringRef relCFStringRef =
|
||
CFStringCreateWithCString(
|
||
kCFAllocatorDefault,
|
||
fileName.toUtf8().constData(),
|
||
kCFStringEncodingUTF8
|
||
);
|
||
CFURLRef relCFURL =
|
||
CFURLCreateWithFileSystemPath(
|
||
kCFAllocatorDefault,
|
||
relCFStringRef,
|
||
kCFURLPOSIXPathStyle,
|
||
false // isDirectory
|
||
);
|
||
CFErrorRef error = 0;
|
||
CFURLRef absCFURL =
|
||
CFURLCreateFilePathURL(
|
||
kCFAllocatorDefault,
|
||
relCFURL,
|
||
&error
|
||
);
|
||
if ( !error )
|
||
{
|
||
static const CFIndex maxAbsPathCStrBufLen = 4096;
|
||
char absPathCStr[maxAbsPathCStrBufLen];
|
||
if ( CFURLGetFileSystemRepresentation(
|
||
absCFURL,
|
||
true, // resolveAgainstBase
|
||
reinterpret_cast<UInt8 *>( &absPathCStr[0] ),
|
||
maxAbsPathCStrBufLen ) )
|
||
{
|
||
fileName = QString( absPathCStr );
|
||
}
|
||
}
|
||
CFRelease( absCFURL );
|
||
CFRelease( relCFURL );
|
||
CFRelease( relCFStringRef );
|
||
}
|
||
#endif
|
||
// seems that some drag and drop operations include an empty url
|
||
// so we test for length to make sure we have something
|
||
if ( !fileName.isEmpty() )
|
||
{
|
||
files << fileName;
|
||
}
|
||
}
|
||
|
||
connect( timer, &QTimer::timeout, this, [this, timer, files]
|
||
{
|
||
for ( const QString &file : qgis::as_const( files ) )
|
||
{
|
||
const QVector<QPointer<QgsLayoutCustomDropHandler >> handlers = QgisApp::instance()->customLayoutDropHandlers();
|
||
for ( QgsLayoutCustomDropHandler *handler : handlers )
|
||
{
|
||
if ( handler && handler->handleFileDrop( iface(), file ) )
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
timer->deleteLater();
|
||
} );
|
||
|
||
event->acceptProposedAction();
|
||
timer->start();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::dragEnterEvent( QDragEnterEvent *event )
|
||
{
|
||
if ( event->mimeData()->hasUrls() )
|
||
{
|
||
event->acceptProposedAction();
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::setTitle( const QString &title )
|
||
{
|
||
mTitle = title;
|
||
updateWindowTitle();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::itemTypeAdded( int id )
|
||
{
|
||
if ( QgsGui::layoutItemGuiRegistry()->itemMetadata( id )->flags() & QgsLayoutItemAbstractGuiMetadata::FlagNoCreationTools )
|
||
return;
|
||
|
||
QString name = QgsGui::layoutItemGuiRegistry()->itemMetadata( id )->visibleName();
|
||
QString groupId = QgsGui::layoutItemGuiRegistry()->itemMetadata( id )->groupId();
|
||
bool nodeBased = QgsGui::layoutItemGuiRegistry()->itemMetadata( id )->isNodeBased();
|
||
QToolButton *groupButton = nullptr;
|
||
QMenu *itemSubmenu = nullptr;
|
||
if ( !groupId.isEmpty() )
|
||
{
|
||
// find existing group toolbutton and submenu, or create new ones if this is the first time the group has been encountered
|
||
const QgsLayoutItemGuiGroup &group = QgsGui::layoutItemGuiRegistry()->itemGroup( groupId );
|
||
QIcon groupIcon = group.icon.isNull() ? QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicShape.svg" ) ) : group.icon;
|
||
QString groupText = tr( "Add %1" ).arg( group.name );
|
||
if ( mItemGroupToolButtons.contains( groupId ) )
|
||
{
|
||
groupButton = mItemGroupToolButtons.value( groupId );
|
||
}
|
||
else
|
||
{
|
||
QToolButton *groupToolButton = new QToolButton( mToolsToolbar );
|
||
groupToolButton->setIcon( groupIcon );
|
||
groupToolButton->setCheckable( true );
|
||
groupToolButton->setPopupMode( QToolButton::InstantPopup );
|
||
groupToolButton->setAutoRaise( true );
|
||
groupToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly );
|
||
groupToolButton->setToolTip( groupText );
|
||
mToolsToolbar->addWidget( groupToolButton );
|
||
mItemGroupToolButtons.insert( groupId, groupToolButton );
|
||
groupButton = groupToolButton;
|
||
}
|
||
|
||
if ( mItemGroupSubmenus.contains( groupId ) )
|
||
{
|
||
itemSubmenu = mItemGroupSubmenus.value( groupId );
|
||
}
|
||
else
|
||
{
|
||
QMenu *groupSubmenu = mItemMenu->addMenu( groupText );
|
||
groupSubmenu->setIcon( groupIcon );
|
||
mItemMenu->addMenu( groupSubmenu );
|
||
mItemGroupSubmenus.insert( groupId, groupSubmenu );
|
||
itemSubmenu = groupSubmenu;
|
||
}
|
||
}
|
||
|
||
// update UI for new item type
|
||
QAction *action = new QAction( tr( "Add %1" ).arg( name ), this );
|
||
action->setToolTip( tr( "Adds a new %1 to the layout" ).arg( name ) );
|
||
action->setCheckable( true );
|
||
action->setData( id );
|
||
action->setIcon( QgsGui::layoutItemGuiRegistry()->itemMetadata( id )->creationIcon() );
|
||
|
||
mToolsActionGroup->addAction( action );
|
||
if ( itemSubmenu )
|
||
itemSubmenu->addAction( action );
|
||
else
|
||
mItemMenu->addAction( action );
|
||
|
||
if ( groupButton )
|
||
groupButton->addAction( action );
|
||
else
|
||
mToolsToolbar->addAction( action );
|
||
|
||
connect( action, &QAction::triggered, this, [this, id, nodeBased]()
|
||
{
|
||
activateNewItemCreationTool( id, nodeBased );
|
||
} );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::statusZoomCombo_currentIndexChanged( int index )
|
||
{
|
||
QVariant data = mStatusZoomCombo->itemData( index );
|
||
if ( data.toInt() == FIT_LAYOUT )
|
||
{
|
||
mView->zoomFull();
|
||
}
|
||
else if ( data.toInt() == FIT_LAYOUT_WIDTH )
|
||
{
|
||
mView->zoomWidth();
|
||
}
|
||
else
|
||
{
|
||
double selectedZoom = data.toDouble();
|
||
if ( mView )
|
||
{
|
||
mView->setZoomLevel( selectedZoom );
|
||
//update zoom combobox text for correct format (one decimal place, trailing % sign)
|
||
whileBlocking( mStatusZoomCombo )->lineEdit()->setText( tr( "%1%" ).arg( selectedZoom * 100.0, 0, 'f', 1 ) );
|
||
}
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::statusZoomCombo_zoomEntered()
|
||
{
|
||
if ( !mView )
|
||
{
|
||
return;
|
||
}
|
||
|
||
//need to remove spaces and "%" characters from input text
|
||
QString zoom = mStatusZoomCombo->currentText().remove( QChar( '%' ) ).trimmed();
|
||
mView->setZoomLevel( zoom.toDouble() / 100 );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::sliderZoomChanged( int value )
|
||
{
|
||
mView->setZoomLevel( value / 100.0 );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::updateStatusZoom()
|
||
{
|
||
if ( !currentLayout() )
|
||
return;
|
||
|
||
double zoomLevel = 0;
|
||
if ( currentLayout()->units() == QgsUnitTypes::LayoutPixels )
|
||
{
|
||
zoomLevel = mView->transform().m11() * 100;
|
||
}
|
||
else
|
||
{
|
||
double dpi = QgsApplication::desktop()->logicalDpiX();
|
||
//monitor dpi is not always correct - so make sure the value is sane
|
||
if ( ( dpi < 60 ) || ( dpi > 1200 ) )
|
||
dpi = 72;
|
||
|
||
//pixel width for 1mm on screen
|
||
double scale100 = dpi / 25.4;
|
||
scale100 = currentLayout()->convertFromLayoutUnits( scale100, QgsUnitTypes::LayoutMillimeters ).length();
|
||
//current zoomLevel
|
||
zoomLevel = mView->transform().m11() * 100 / scale100;
|
||
}
|
||
whileBlocking( mStatusZoomCombo )->lineEdit()->setText( tr( "%1%" ).arg( zoomLevel, 0, 'f', 1 ) );
|
||
whileBlocking( mStatusZoomSlider )->setValue( static_cast< int >( zoomLevel ) );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::updateStatusCursorPos( QPointF position )
|
||
{
|
||
if ( !mView->currentLayout() )
|
||
{
|
||
return;
|
||
}
|
||
|
||
//convert cursor position to position on current page
|
||
QPointF pagePosition = mLayout->pageCollection()->positionOnPage( position );
|
||
int currentPage = mLayout->pageCollection()->pageNumberForPoint( position );
|
||
|
||
QString unit = QgsUnitTypes::toAbbreviatedString( mLayout->units() );
|
||
mStatusCursorXLabel->setText( tr( "x: %1 %2" ).arg( pagePosition.x() ).arg( unit ) );
|
||
mStatusCursorYLabel->setText( tr( "y: %1 %2" ).arg( pagePosition.y() ).arg( unit ) );
|
||
mStatusCursorPageLabel->setText( tr( "page: %1" ).arg( currentPage + 1 ) );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::toggleFullScreen( bool enabled )
|
||
{
|
||
if ( enabled )
|
||
{
|
||
showFullScreen();
|
||
}
|
||
else
|
||
{
|
||
showNormal();
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::addPages()
|
||
{
|
||
QgsLayoutAddPagesDialog dlg( this );
|
||
dlg.setLayout( mLayout );
|
||
|
||
if ( dlg.exec() )
|
||
{
|
||
int firstPagePosition = dlg.beforePage() - 1;
|
||
switch ( dlg.pagePosition() )
|
||
{
|
||
case QgsLayoutAddPagesDialog::BeforePage:
|
||
break;
|
||
|
||
case QgsLayoutAddPagesDialog::AfterPage:
|
||
firstPagePosition = firstPagePosition + 1;
|
||
break;
|
||
|
||
case QgsLayoutAddPagesDialog::AtEnd:
|
||
firstPagePosition = mLayout->pageCollection()->pageCount();
|
||
break;
|
||
|
||
}
|
||
|
||
if ( dlg.numberPages() > 1 )
|
||
mLayout->undoStack()->beginMacro( tr( "Add Pages" ) );
|
||
for ( int i = 0; i < dlg.numberPages(); ++i )
|
||
{
|
||
QgsLayoutItemPage *page = new QgsLayoutItemPage( mLayout );
|
||
page->setPageSize( dlg.pageSize() );
|
||
mLayout->pageCollection()->insertPage( page, firstPagePosition + i );
|
||
}
|
||
if ( dlg.numberPages() > 1 )
|
||
mLayout->undoStack()->endMacro();
|
||
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::statusMessageReceived( const QString &message )
|
||
{
|
||
mStatusBar->showMessage( message );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::dockVisibilityChanged( bool visible )
|
||
{
|
||
if ( visible )
|
||
{
|
||
whileBlocking( mActionHidePanels )->setChecked( false );
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::undoRedoOccurredForItems( const QSet<QString> &itemUuids )
|
||
{
|
||
mBlockItemOptions = true;
|
||
|
||
mLayout->deselectAll();
|
||
QgsLayoutItem *focusItem = nullptr;
|
||
for ( const QString &uuid : itemUuids )
|
||
{
|
||
QgsLayoutItem *item = mLayout->itemByUuid( uuid );
|
||
if ( !item )
|
||
continue;
|
||
|
||
item->setSelected( true );
|
||
focusItem = item;
|
||
}
|
||
mBlockItemOptions = false;
|
||
|
||
if ( focusItem )
|
||
showItemOptions( focusItem, false );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::saveAsTemplate()
|
||
{
|
||
//show file dialog
|
||
QgsSettings settings;
|
||
QString lastSaveDir = settings.value( QStringLiteral( "lastComposerTemplateDir" ), QDir::homePath(), QgsSettings::App ).toString();
|
||
#ifdef Q_OS_MAC
|
||
QgisApp::instance()->activateWindow();
|
||
this->raise();
|
||
#endif
|
||
QString saveFileName = QFileDialog::getSaveFileName(
|
||
this,
|
||
tr( "Save template" ),
|
||
lastSaveDir,
|
||
tr( "Layout templates" ) + " (*.qpt *.QPT)" );
|
||
if ( saveFileName.isEmpty() )
|
||
return;
|
||
|
||
QFileInfo saveFileInfo( saveFileName );
|
||
//check if suffix has been added
|
||
if ( saveFileInfo.suffix().isEmpty() )
|
||
{
|
||
QString saveFileNameWithSuffix = saveFileName.append( ".qpt" );
|
||
saveFileInfo = QFileInfo( saveFileNameWithSuffix );
|
||
}
|
||
settings.setValue( QStringLiteral( "lastComposerTemplateDir" ), saveFileInfo.absolutePath(), QgsSettings::App );
|
||
|
||
QgsReadWriteContext context;
|
||
context.setPathResolver( QgsProject::instance()->pathResolver() );
|
||
if ( !currentLayout()->saveAsTemplate( saveFileName, context ) )
|
||
{
|
||
QMessageBox::warning( this, tr( "Save Template" ), tr( "Error creating template file." ) );
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::addItemsFromTemplate()
|
||
{
|
||
if ( !currentLayout() )
|
||
return;
|
||
|
||
QgsSettings settings;
|
||
QString openFileDir = settings.value( QStringLiteral( "lastComposerTemplateDir" ), QDir::homePath(), QgsSettings::App ).toString();
|
||
QString openFileString = QFileDialog::getOpenFileName( nullptr, tr( "Load template" ), openFileDir, tr( "Layout templates" ) + " (*.qpt *.QPT)" );
|
||
|
||
if ( openFileString.isEmpty() )
|
||
{
|
||
return; //canceled by the user
|
||
}
|
||
|
||
QFileInfo openFileInfo( openFileString );
|
||
settings.setValue( QStringLiteral( "LastComposerTemplateDir" ), openFileInfo.absolutePath(), QgsSettings::App );
|
||
|
||
QFile templateFile( openFileString );
|
||
if ( !templateFile.open( QIODevice::ReadOnly ) )
|
||
{
|
||
QMessageBox::warning( this, tr( "Load from Template" ), tr( "Could not read template file." ) );
|
||
return;
|
||
}
|
||
|
||
QDomDocument templateDoc;
|
||
QgsReadWriteContext context;
|
||
context.setPathResolver( QgsProject::instance()->pathResolver() );
|
||
if ( templateDoc.setContent( &templateFile ) )
|
||
{
|
||
bool ok = false;
|
||
QList< QgsLayoutItem * > items = currentLayout()->loadFromTemplate( templateDoc, context, false, &ok );
|
||
if ( !ok )
|
||
{
|
||
QMessageBox::warning( this, tr( "Load from Template" ), tr( "Could not read template file." ) );
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
whileBlocking( currentLayout() )->deselectAll();
|
||
selectItems( items );
|
||
}
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::duplicate()
|
||
{
|
||
QString newTitle;
|
||
if ( !QgisApp::instance()->uniqueLayoutTitle( this, newTitle, false, masterLayout()->layoutType(), tr( "%1 copy" ).arg( masterLayout()->name() ) ) )
|
||
{
|
||
return;
|
||
}
|
||
|
||
// provide feedback, since loading of template into duplicate layout will be hidden
|
||
QDialog *dlg = new QgsBusyIndicatorDialog( tr( "Duplicating layout…" ) );
|
||
dlg->setStyleSheet( QgisApp::instance()->styleSheet() );
|
||
dlg->show();
|
||
|
||
QgsLayoutDesignerDialog *newDialog = QgisApp::instance()->duplicateLayout( mMasterLayout, newTitle );
|
||
|
||
dlg->close();
|
||
delete dlg;
|
||
dlg = nullptr;
|
||
|
||
if ( !newDialog )
|
||
{
|
||
QMessageBox::warning( this, tr( "Duplicate Layout" ),
|
||
tr( "Layout duplication failed." ) );
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::saveProject()
|
||
{
|
||
QgisApp::instance()->actionSaveProject()->trigger();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::newLayout()
|
||
{
|
||
QString title;
|
||
if ( !QgisApp::instance()->uniqueLayoutTitle( this, title, true, mMasterLayout->layoutType() ) )
|
||
{
|
||
return;
|
||
}
|
||
|
||
switch ( mMasterLayout->layoutType() )
|
||
{
|
||
case QgsMasterLayoutInterface::PrintLayout:
|
||
QgisApp::instance()->createNewPrintLayout( title );
|
||
break;
|
||
|
||
case QgsMasterLayoutInterface::Report:
|
||
QgisApp::instance()->createNewReport( title );
|
||
break;
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::showManager()
|
||
{
|
||
// NOTE: Avoid crash where composer that spawned modal manager from toolbar ends up
|
||
// being deleted by user, but event loop tries to return to layout on manager close
|
||
// (does not seem to be an issue for menu action)
|
||
QTimer::singleShot( 0, this, [ = ]
|
||
{
|
||
QgisApp::instance()->showLayoutManager();
|
||
} );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::renameLayout()
|
||
{
|
||
QString currentTitle = masterLayout()->name();
|
||
QString newTitle;
|
||
if ( !QgisApp::instance()->uniqueLayoutTitle( this, newTitle, false, masterLayout()->layoutType(), currentTitle ) )
|
||
{
|
||
return;
|
||
}
|
||
masterLayout()->setName( newTitle );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::deleteLayout()
|
||
{
|
||
if ( QMessageBox::question( this, tr( "Delete Layout" ), tr( "Are you sure you want to delete the layout “%1”?" ).arg( masterLayout()->name() ),
|
||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes )
|
||
return;
|
||
|
||
masterLayout()->layoutProject()->layoutManager()->removeLayout( masterLayout() );
|
||
close();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::print()
|
||
{
|
||
if ( containsWmsLayers() )
|
||
{
|
||
showWmsPrintingWarning();
|
||
}
|
||
|
||
if ( requiresRasterization() )
|
||
{
|
||
showRasterizationWarning();
|
||
}
|
||
|
||
if ( currentLayout()->pageCollection()->pageCount() == 0 )
|
||
return;
|
||
|
||
// get orientation from first page
|
||
QgsLayoutItemPage::Orientation orientation = currentLayout()->pageCollection()->page( 0 )->orientation();
|
||
|
||
//set printer page orientation
|
||
setPrinterPageOrientation( orientation );
|
||
|
||
QPrintDialog printDialog( printer(), nullptr );
|
||
if ( printDialog.exec() != QDialog::Accepted )
|
||
{
|
||
return;
|
||
}
|
||
|
||
|
||
mView->setPaintingEnabled( false );
|
||
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
|
||
|
||
QgsLayoutExporter::PrintExportSettings printSettings;
|
||
printSettings.rasterizeWholeImage = mLayout->customProperty( QStringLiteral( "rasterize" ), false ).toBool();
|
||
|
||
QgsProxyProgressTask *proxyTask = new QgsProxyProgressTask( tr( "Printing “%1”" ).arg( mMasterLayout->name() ) );
|
||
QgsApplication::taskManager()->addTask( proxyTask );
|
||
|
||
// force a refresh, to e.g. update data defined properties, tables, etc
|
||
mLayout->refresh();
|
||
|
||
QgsLayoutExporter exporter( mLayout );
|
||
QString printerName = printer()->printerName();
|
||
QPrinter *p = printer();
|
||
p->setDocName( mMasterLayout->name() );
|
||
QgsLayoutExporter::ExportResult result = exporter.print( *p, printSettings );
|
||
|
||
proxyTask->finalize( result == QgsLayoutExporter::Success );
|
||
|
||
switch ( result )
|
||
{
|
||
case QgsLayoutExporter::Success:
|
||
{
|
||
QString message;
|
||
if ( !printerName.isEmpty() )
|
||
{
|
||
message = tr( "Successfully printed layout to %1." ).arg( printerName );
|
||
}
|
||
else
|
||
{
|
||
message = tr( "Successfully printed layout." );
|
||
}
|
||
mMessageBar->pushMessage( tr( "Print layout" ),
|
||
message,
|
||
Qgis::Success, 0 );
|
||
break;
|
||
}
|
||
|
||
case QgsLayoutExporter::PrintError:
|
||
{
|
||
QString message;
|
||
if ( !printerName.isEmpty() )
|
||
{
|
||
message = tr( "Could not create print device for %1." ).arg( printerName );
|
||
}
|
||
else
|
||
{
|
||
message = tr( "Could not create print device." );
|
||
}
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Print Layout" ),
|
||
message,
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
}
|
||
|
||
case QgsLayoutExporter::MemoryError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Memory Allocation Error" ),
|
||
tr( "Printing the layout "
|
||
"resulted in a memory overflow.\n\n"
|
||
"Please try a lower resolution or a smaller paper size." ),
|
||
QMessageBox::Ok, QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::FileError:
|
||
case QgsLayoutExporter::SvgLayerError:
|
||
case QgsLayoutExporter::IteratorError:
|
||
case QgsLayoutExporter::Canceled:
|
||
// no meaning for PDF exports, will not be encountered
|
||
break;
|
||
}
|
||
|
||
mView->setPaintingEnabled( true );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::exportToRaster()
|
||
{
|
||
if ( containsWmsLayers() )
|
||
showWmsPrintingWarning();
|
||
|
||
if ( !showFileSizeWarning() )
|
||
return;
|
||
|
||
QString outputFileName;
|
||
QgsLayoutAtlas *printAtlas = atlas();
|
||
QString lastUsedDir = defaultExportPath();
|
||
if ( printAtlas && printAtlas->enabled() && mActionAtlasPreview->isChecked() )
|
||
{
|
||
outputFileName = QDir( lastUsedDir ).filePath( QgsFileUtils::stringToSafeFilename( printAtlas->currentFilename() ) );
|
||
}
|
||
else
|
||
{
|
||
outputFileName = QDir( lastUsedDir ).filePath( QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) );
|
||
}
|
||
|
||
#ifdef Q_OS_MAC
|
||
QgisApp::instance()->activateWindow();
|
||
this->raise();
|
||
#endif
|
||
QPair<QString, QString> fileNExt = QgsGuiUtils::getSaveAsImageName( this, tr( "Save Layout As" ), outputFileName );
|
||
this->activateWindow();
|
||
|
||
if ( fileNExt.first.isEmpty() )
|
||
{
|
||
return;
|
||
}
|
||
|
||
setLastExportPath( fileNExt.first );
|
||
|
||
QgsLayoutExporter::ImageExportSettings settings;
|
||
QSize imageSize;
|
||
if ( !getRasterExportSettings( settings, imageSize ) )
|
||
return;
|
||
|
||
mView->setPaintingEnabled( false );
|
||
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
|
||
QgsProxyProgressTask *proxyTask = new QgsProxyProgressTask( tr( "Exporting “%1”" ).arg( mMasterLayout->name() ) );
|
||
QgsApplication::taskManager()->addTask( proxyTask );
|
||
|
||
// force a refresh, to e.g. update data defined properties, tables, etc
|
||
mLayout->refresh();
|
||
|
||
QgsLayoutExporter exporter( mLayout );
|
||
|
||
QgsLayoutExporter::ExportResult result = exporter.exportToImage( fileNExt.first, settings );
|
||
|
||
proxyTask->finalize( result == QgsLayoutExporter::Success );
|
||
|
||
switch ( result )
|
||
{
|
||
case QgsLayoutExporter::Success:
|
||
mMessageBar->pushMessage( tr( "Export layout" ),
|
||
tr( "Successfully exported layout to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( fileNExt.first ).toString(), QDir::toNativeSeparators( fileNExt.first ) ),
|
||
Qgis::Success, 0 );
|
||
break;
|
||
|
||
case QgsLayoutExporter::PrintError:
|
||
case QgsLayoutExporter::SvgLayerError:
|
||
case QgsLayoutExporter::IteratorError:
|
||
case QgsLayoutExporter::Canceled:
|
||
// no meaning for raster exports, will not be encountered
|
||
break;
|
||
|
||
case QgsLayoutExporter::FileError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Image Export Error" ),
|
||
tr( "Cannot write to %1.\n\nThis file may be open in another application." ).arg( QDir::toNativeSeparators( exporter.errorFile() ) ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::MemoryError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Image Export 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( QDir::toNativeSeparators( exporter.errorFile() ) ).arg( imageSize.width() ).arg( imageSize.height() ).arg( settings.dpi ),
|
||
QMessageBox::Ok, QMessageBox::Ok );
|
||
break;
|
||
|
||
|
||
}
|
||
mView->setPaintingEnabled( true );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::exportToPdf()
|
||
{
|
||
if ( containsWmsLayers() )
|
||
{
|
||
showWmsPrintingWarning();
|
||
}
|
||
|
||
if ( requiresRasterization() )
|
||
{
|
||
showRasterizationWarning();
|
||
}
|
||
|
||
if ( containsAdvancedEffects() && ( mLayout->customProperty( QStringLiteral( "forceVector" ), false ).toBool() ) )
|
||
{
|
||
showForceVectorWarning();
|
||
}
|
||
|
||
const QString exportPath = defaultExportPath();
|
||
QString outputFileName;
|
||
|
||
QgsLayoutAtlas *printAtlas = atlas();
|
||
if ( printAtlas && printAtlas->enabled() && mActionAtlasPreview->isChecked() )
|
||
{
|
||
outputFileName = QDir( exportPath ).filePath( QgsFileUtils::stringToSafeFilename( printAtlas->currentFilename() ) + QStringLiteral( ".pdf" ) );
|
||
}
|
||
else
|
||
{
|
||
outputFileName = exportPath + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".pdf" );
|
||
}
|
||
|
||
#ifdef Q_OS_MAC
|
||
QgisApp::instance()->activateWindow();
|
||
this->raise();
|
||
#endif
|
||
outputFileName = QFileDialog::getSaveFileName(
|
||
this,
|
||
tr( "Export to PDF" ),
|
||
outputFileName,
|
||
tr( "PDF Format" ) + " (*.pdf *.PDF)" );
|
||
this->activateWindow();
|
||
if ( outputFileName.isEmpty() )
|
||
{
|
||
return;
|
||
}
|
||
|
||
if ( !outputFileName.endsWith( QLatin1String( ".pdf" ), Qt::CaseInsensitive ) )
|
||
{
|
||
outputFileName += QLatin1String( ".pdf" );
|
||
}
|
||
|
||
setLastExportPath( outputFileName );
|
||
|
||
mView->setPaintingEnabled( false );
|
||
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
|
||
|
||
QgsLayoutExporter::PdfExportSettings pdfSettings;
|
||
if ( !getPdfExportSettings( pdfSettings ) )
|
||
return;
|
||
|
||
QgsProxyProgressTask *proxyTask = new QgsProxyProgressTask( tr( "Exporting “%1”" ).arg( mMasterLayout->name() ) );
|
||
QgsApplication::taskManager()->addTask( proxyTask );
|
||
|
||
pdfSettings.rasterizeWholeImage = mLayout->customProperty( QStringLiteral( "rasterize" ), false ).toBool();
|
||
|
||
// force a refresh, to e.g. update data defined properties, tables, etc
|
||
mLayout->refresh();
|
||
|
||
QgsLayoutExporter exporter( mLayout );
|
||
QgsLayoutExporter::ExportResult result = exporter.exportToPdf( outputFileName, pdfSettings );
|
||
|
||
proxyTask->finalize( result == QgsLayoutExporter::Success );
|
||
|
||
switch ( result )
|
||
{
|
||
case QgsLayoutExporter::Success:
|
||
{
|
||
mMessageBar->pushMessage( tr( "Export layout" ),
|
||
tr( "Successfully exported layout to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( outputFileName ).toString(), QDir::toNativeSeparators( outputFileName ) ),
|
||
Qgis::Success, 0 );
|
||
break;
|
||
}
|
||
|
||
case QgsLayoutExporter::FileError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Export to PDF" ),
|
||
tr( "Cannot write to %1.\n\nThis file may be open in another application." ).arg( outputFileName ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::PrintError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Export to PDF" ),
|
||
tr( "Could not create print device." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
|
||
case QgsLayoutExporter::MemoryError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Export to PDF" ),
|
||
tr( "Exporting the PDF "
|
||
"resulted in a memory overflow.\n\n"
|
||
"Please try a lower resolution or a smaller paper size." ),
|
||
QMessageBox::Ok, QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::SvgLayerError:
|
||
case QgsLayoutExporter::IteratorError:
|
||
case QgsLayoutExporter::Canceled:
|
||
// no meaning for PDF exports, will not be encountered
|
||
break;
|
||
}
|
||
|
||
mView->setPaintingEnabled( true );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::exportToSvg()
|
||
{
|
||
if ( containsWmsLayers() )
|
||
{
|
||
showWmsPrintingWarning();
|
||
}
|
||
|
||
showSvgExportWarning();
|
||
|
||
const QString defaultPath = defaultExportPath();
|
||
QString outputFileName = QgsFileUtils::stringToSafeFilename( mMasterLayout->name() );
|
||
|
||
QgsLayoutAtlas *printAtlas = atlas();
|
||
if ( printAtlas && printAtlas->enabled() && mActionAtlasPreview->isChecked() )
|
||
{
|
||
outputFileName = QDir( defaultPath ).filePath( QgsFileUtils::stringToSafeFilename( printAtlas->currentFilename() + QStringLiteral( ".svg" ) ) );
|
||
}
|
||
else
|
||
{
|
||
outputFileName = defaultPath + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".svg" );
|
||
}
|
||
|
||
#ifdef Q_OS_MAC
|
||
QgisApp::instance()->activateWindow();
|
||
this->raise();
|
||
#endif
|
||
outputFileName = QFileDialog::getSaveFileName(
|
||
this,
|
||
tr( "Export to SVG" ),
|
||
outputFileName,
|
||
tr( "SVG Format" ) + " (*.svg *.SVG)" );
|
||
this->activateWindow();
|
||
if ( outputFileName.isEmpty() )
|
||
{
|
||
return;
|
||
}
|
||
|
||
if ( !outputFileName.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
|
||
{
|
||
outputFileName += QLatin1String( ".svg" );
|
||
}
|
||
|
||
setLastExportPath( outputFileName );
|
||
|
||
QgsLayoutExporter::SvgExportSettings svgSettings;
|
||
if ( !getSvgExportSettings( svgSettings ) )
|
||
return;
|
||
|
||
mView->setPaintingEnabled( false );
|
||
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
|
||
|
||
QgsProxyProgressTask *proxyTask = new QgsProxyProgressTask( tr( "Exporting “%1”" ).arg( mMasterLayout->name() ) );
|
||
QgsApplication::taskManager()->addTask( proxyTask );
|
||
|
||
// force a refresh, to e.g. update data defined properties, tables, etc
|
||
mLayout->refresh();
|
||
|
||
QgsLayoutExporter exporter( mLayout );
|
||
QgsLayoutExporter::ExportResult result = exporter.exportToSvg( outputFileName, svgSettings );
|
||
|
||
proxyTask->finalize( result == QgsLayoutExporter::Success );
|
||
|
||
switch ( result )
|
||
{
|
||
case QgsLayoutExporter::Success:
|
||
{
|
||
mMessageBar->pushMessage( tr( "Export layout" ),
|
||
tr( "Successfully exported layout to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( outputFileName ).toString(), QDir::toNativeSeparators( outputFileName ) ),
|
||
Qgis::Success, 0 );
|
||
break;
|
||
}
|
||
|
||
case QgsLayoutExporter::FileError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Export to SVG" ),
|
||
tr( "Cannot write to %1.\n\nThis file may be open in another application." ).arg( outputFileName ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::SvgLayerError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Export to SVG" ),
|
||
tr( "Cannot create layered SVG file %1." ).arg( outputFileName ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::PrintError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Export to SVG" ),
|
||
tr( "Could not create print device." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
|
||
case QgsLayoutExporter::MemoryError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Export to SVG" ),
|
||
tr( "Exporting the SVG "
|
||
"resulted in a memory overflow.\n\n"
|
||
"Please try a lower resolution or a smaller paper size." ),
|
||
QMessageBox::Ok, QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::IteratorError:
|
||
case QgsLayoutExporter::Canceled:
|
||
// no meaning here
|
||
break;
|
||
}
|
||
|
||
mView->setPaintingEnabled( true );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::atlasPreviewTriggered( bool checked )
|
||
{
|
||
QgsPrintLayout *printLayout = qobject_cast< QgsPrintLayout * >( mLayout );
|
||
if ( !printLayout )
|
||
return;
|
||
QgsLayoutAtlas *atlas = printLayout->atlas();
|
||
|
||
//check if composition has an atlas map enabled
|
||
if ( checked && !atlas->enabled() )
|
||
{
|
||
//no atlas current enabled
|
||
mMessageBar->pushWarning( tr( "Atlas" ),
|
||
tr( "Atlas is not enabled for this layout!" ) );
|
||
whileBlocking( mActionAtlasPreview )->setChecked( false );
|
||
return;
|
||
}
|
||
|
||
//toggle other controls depending on whether atlas preview is active
|
||
mActionAtlasFirst->setEnabled( checked );
|
||
mActionAtlasLast->setEnabled( checked );
|
||
mActionAtlasNext->setEnabled( checked );
|
||
mActionAtlasPrev->setEnabled( checked );
|
||
mAtlasPageComboBox->setEnabled( checked );
|
||
|
||
if ( checked )
|
||
{
|
||
loadAtlasPredefinedScalesFromProject();
|
||
}
|
||
|
||
if ( checked )
|
||
{
|
||
if ( !atlas->beginRender() )
|
||
{
|
||
atlas->endRender();
|
||
//something went wrong, e.g., no matching features
|
||
mMessageBar->pushWarning( tr( "Atlas" ), tr( "No matching atlas features found!" ) );
|
||
mActionAtlasPreview->blockSignals( true );
|
||
mActionAtlasPreview->setChecked( false );
|
||
mActionAtlasFirst->setEnabled( false );
|
||
mActionAtlasLast->setEnabled( false );
|
||
mActionAtlasNext->setEnabled( false );
|
||
mActionAtlasPrev->setEnabled( false );
|
||
mAtlasPageComboBox->setEnabled( false );
|
||
mActionAtlasPreview->blockSignals( false );
|
||
}
|
||
else
|
||
{
|
||
QgisApp::instance()->mapCanvas()->stopRendering();
|
||
atlas->first();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
atlas->endRender();
|
||
mView->setSectionLabel( QString() );
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::atlasPageComboEditingFinished()
|
||
{
|
||
QString text = mAtlasPageComboBox->lineEdit()->text();
|
||
|
||
//find matching record in combo box
|
||
int page = -1; //note - first page starts at 1, not 0
|
||
for ( int i = 0; i < mAtlasPageComboBox->count(); ++i )
|
||
{
|
||
if ( text.compare( mAtlasPageComboBox->itemData( i, Qt::UserRole + 1 ).toString(), Qt::CaseInsensitive ) == 0
|
||
|| text.compare( mAtlasPageComboBox->itemData( i, Qt::UserRole + 2 ).toString(), Qt::CaseInsensitive ) == 0
|
||
|| QString::number( i + 1 ) == text )
|
||
{
|
||
page = i + 1;
|
||
break;
|
||
}
|
||
}
|
||
bool ok = ( page > 0 );
|
||
|
||
QgsPrintLayout *printLayout = qobject_cast< QgsPrintLayout * >( mLayout );
|
||
if ( !printLayout )
|
||
return;
|
||
QgsLayoutAtlas *atlas = printLayout->atlas();
|
||
|
||
if ( !ok || page > atlas->count() || page < 1 )
|
||
{
|
||
whileBlocking( mAtlasPageComboBox )->setCurrentIndex( atlas->currentFeatureNumber() );
|
||
}
|
||
else if ( page != atlas->currentFeatureNumber() + 1 )
|
||
{
|
||
QgisApp::instance()->mapCanvas()->stopRendering();
|
||
loadAtlasPredefinedScalesFromProject();
|
||
atlas->seekTo( page - 1 );
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::atlasNext()
|
||
{
|
||
QgsLayoutAtlas *printAtlas = atlas();
|
||
if ( !printAtlas )
|
||
return;
|
||
|
||
QgisApp::instance()->mapCanvas()->stopRendering();
|
||
|
||
loadAtlasPredefinedScalesFromProject();
|
||
printAtlas->next();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::atlasPrevious()
|
||
{
|
||
QgsLayoutAtlas *printAtlas = atlas();
|
||
if ( !printAtlas )
|
||
return;
|
||
|
||
QgisApp::instance()->mapCanvas()->stopRendering();
|
||
|
||
loadAtlasPredefinedScalesFromProject();
|
||
printAtlas->previous();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::atlasFirst()
|
||
{
|
||
QgsLayoutAtlas *printAtlas = atlas();
|
||
if ( !printAtlas )
|
||
return;
|
||
|
||
QgisApp::instance()->mapCanvas()->stopRendering();
|
||
|
||
loadAtlasPredefinedScalesFromProject();
|
||
printAtlas->first();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::atlasLast()
|
||
{
|
||
QgsLayoutAtlas *printAtlas = atlas();
|
||
if ( !printAtlas )
|
||
return;
|
||
|
||
QgisApp::instance()->mapCanvas()->stopRendering();
|
||
|
||
loadAtlasPredefinedScalesFromProject();
|
||
printAtlas->last();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::printAtlas()
|
||
{
|
||
QgsLayoutAtlas *printAtlas = atlas();
|
||
if ( !printAtlas || !printAtlas->enabled() )
|
||
return;
|
||
|
||
loadAtlasPredefinedScalesFromProject();
|
||
|
||
if ( containsWmsLayers() )
|
||
{
|
||
showWmsPrintingWarning();
|
||
}
|
||
|
||
if ( requiresRasterization() )
|
||
{
|
||
showRasterizationWarning();
|
||
}
|
||
|
||
if ( currentLayout()->pageCollection()->pageCount() == 0 )
|
||
return;
|
||
|
||
// get orientation from first page
|
||
QgsLayoutItemPage::Orientation orientation = currentLayout()->pageCollection()->page( 0 )->orientation();
|
||
|
||
//set printer page orientation
|
||
setPrinterPageOrientation( orientation );
|
||
|
||
QPrintDialog printDialog( printer(), nullptr );
|
||
if ( printDialog.exec() != QDialog::Accepted )
|
||
{
|
||
return;
|
||
}
|
||
|
||
mView->setPaintingEnabled( false );
|
||
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
|
||
|
||
QgsLayoutExporter::PrintExportSettings printSettings;
|
||
printSettings.rasterizeWholeImage = mLayout->customProperty( QStringLiteral( "rasterize" ), false ).toBool();
|
||
|
||
QString error;
|
||
std::unique_ptr< QgsFeedback > feedback = qgis::make_unique< QgsFeedback >();
|
||
std::unique_ptr< QProgressDialog > progressDialog = qgis::make_unique< QProgressDialog >( tr( "Printing maps…" ), tr( "Abort" ), 0, 100, this );
|
||
progressDialog->setWindowTitle( tr( "Printing Atlas" ) );
|
||
|
||
QgsProxyProgressTask *proxyTask = new QgsProxyProgressTask( tr( "Printing “%1”" ).arg( mMasterLayout->name() ) );
|
||
|
||
connect( feedback.get(), &QgsFeedback::progressChanged, this, [ & ]( double progress )
|
||
{
|
||
progressDialog->setValue( static_cast< int >( progress ) );
|
||
progressDialog->setLabelText( feedback->property( "progress" ).toString() ) ;
|
||
|
||
proxyTask->setProxyProgress( progress );
|
||
|
||
#ifdef Q_OS_LINUX
|
||
// For some reason on Windows hasPendingEvents() always return true,
|
||
// but one iteration is actually enough on Windows to get good interactivity
|
||
// whereas on Linux we must allow for far more iterations.
|
||
// For safety limit the number of iterations
|
||
int nIters = 0;
|
||
while ( QCoreApplication::hasPendingEvents() && ++nIters < 100 )
|
||
#endif
|
||
{
|
||
QCoreApplication::processEvents();
|
||
}
|
||
|
||
} );
|
||
connect( progressDialog.get(), &QProgressDialog::canceled, this, [ & ]
|
||
{
|
||
feedback->cancel();
|
||
} );
|
||
|
||
QgsApplication::taskManager()->addTask( proxyTask );
|
||
|
||
QPrinter *p = printer();
|
||
p->setDocName( mMasterLayout->name() );
|
||
QString printerName = p->printerName();
|
||
QgsLayoutExporter::ExportResult result = QgsLayoutExporter::print( printAtlas, *p, printSettings, error, feedback.get() );
|
||
|
||
proxyTask->finalize( result == QgsLayoutExporter::Success );
|
||
|
||
switch ( result )
|
||
{
|
||
case QgsLayoutExporter::Success:
|
||
{
|
||
QString message;
|
||
if ( !printerName.isEmpty() )
|
||
{
|
||
message = tr( "Successfully printed atlas to %1." ).arg( printerName );
|
||
}
|
||
else
|
||
{
|
||
message = tr( "Successfully printed atlas." );
|
||
}
|
||
mMessageBar->pushMessage( tr( "Print atlas" ),
|
||
message,
|
||
Qgis::Success, 0 );
|
||
break;
|
||
}
|
||
|
||
case QgsLayoutExporter::PrintError:
|
||
{
|
||
QString message;
|
||
if ( !printerName.isEmpty() )
|
||
{
|
||
message = tr( "Could not create print device for %1." ).arg( printerName );
|
||
}
|
||
else
|
||
{
|
||
message = tr( "Could not create print device." );
|
||
}
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Print Atlas" ),
|
||
message,
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
}
|
||
|
||
case QgsLayoutExporter::MemoryError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Print Atlas" ),
|
||
tr( "Printing the layout "
|
||
"resulted in a memory overflow.\n\n"
|
||
"Please try a lower resolution or a smaller paper size." ),
|
||
QMessageBox::Ok, QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::IteratorError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Print Atlas" ),
|
||
tr( "Error encountered while printing atlas." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::FileError:
|
||
case QgsLayoutExporter::SvgLayerError:
|
||
case QgsLayoutExporter::Canceled:
|
||
// no meaning for PDF exports, will not be encountered
|
||
break;
|
||
}
|
||
|
||
mView->setPaintingEnabled( true );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::exportAtlasToRaster()
|
||
{
|
||
QgsLayoutAtlas *printAtlas = atlas();
|
||
if ( !printAtlas || !printAtlas->enabled() )
|
||
return;
|
||
|
||
loadAtlasPredefinedScalesFromProject();
|
||
|
||
// else, it has an atlas to render, so a directory must first be selected
|
||
if ( printAtlas->filenameExpression().isEmpty() )
|
||
{
|
||
int res = QMessageBox::warning( nullptr, tr( "Export Atlas as Image" ),
|
||
tr( "The filename expression is empty. A default one will be used instead." ),
|
||
QMessageBox::Ok | QMessageBox::Cancel,
|
||
QMessageBox::Ok );
|
||
if ( res == QMessageBox::Cancel )
|
||
{
|
||
return;
|
||
}
|
||
QString error;
|
||
printAtlas->setFilenameExpression( QStringLiteral( "'output_'||@atlas_featurenumber" ), error );
|
||
}
|
||
|
||
QString lastUsedDir = defaultExportPath();
|
||
|
||
QFileDialog dlg( this, tr( "Export Atlas to Directory" ) );
|
||
dlg.setFileMode( QFileDialog::Directory );
|
||
dlg.setOption( QFileDialog::ShowDirsOnly, true );
|
||
dlg.setDirectory( lastUsedDir );
|
||
if ( !dlg.exec() )
|
||
{
|
||
return;
|
||
}
|
||
|
||
const QStringList files = dlg.selectedFiles();
|
||
if ( files.empty() || files.at( 0 ).isEmpty() )
|
||
{
|
||
return;
|
||
}
|
||
QString dir = files.at( 0 );
|
||
QString format = mLayout->customProperty( QStringLiteral( "atlasRasterFormat" ), QStringLiteral( "png" ) ).toString();
|
||
QString fileExt = '.' + format;
|
||
if ( dir.isEmpty() )
|
||
{
|
||
return;
|
||
}
|
||
setLastExportPath( dir );
|
||
|
||
// test directory (if it exists and is writable)
|
||
if ( !QDir( dir ).exists() || !QFileInfo( dir ).isWritable() )
|
||
{
|
||
QMessageBox::warning( nullptr, tr( "Export Atlas" ),
|
||
tr( "Unable to write into the given output directory. Canceling." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
return;
|
||
}
|
||
|
||
if ( containsWmsLayers() )
|
||
showWmsPrintingWarning();
|
||
|
||
if ( !showFileSizeWarning() )
|
||
return;
|
||
|
||
#ifdef Q_OS_MAC
|
||
QgisApp::instance()->activateWindow();
|
||
this->raise();
|
||
#endif
|
||
|
||
QgsLayoutExporter::ImageExportSettings settings;
|
||
QSize imageSize;
|
||
if ( !getRasterExportSettings( settings, imageSize ) )
|
||
return;
|
||
|
||
mView->setPaintingEnabled( false );
|
||
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
|
||
|
||
QString error;
|
||
std::unique_ptr< QgsFeedback > feedback = qgis::make_unique< QgsFeedback >();
|
||
std::unique_ptr< QProgressDialog > progressDialog = qgis::make_unique< QProgressDialog >( tr( "Rendering maps…" ), tr( "Abort" ), 0, 100, this );
|
||
progressDialog->setWindowTitle( tr( "Exporting Atlas" ) );
|
||
|
||
QgsProxyProgressTask *proxyTask = new QgsProxyProgressTask( tr( "Exporting “%1”" ).arg( mMasterLayout->name() ) );
|
||
|
||
connect( feedback.get(), &QgsFeedback::progressChanged, this, [ & ]( double progress )
|
||
{
|
||
progressDialog->setValue( static_cast< int >( progress ) );
|
||
progressDialog->setLabelText( feedback->property( "progress" ).toString() ) ;
|
||
|
||
proxyTask->setProxyProgress( progress );
|
||
|
||
#ifdef Q_OS_LINUX
|
||
// For some reason on Windows hasPendingEvents() always return true,
|
||
// but one iteration is actually enough on Windows to get good interactivity
|
||
// whereas on Linux we must allow for far more iterations.
|
||
// For safety limit the number of iterations
|
||
int nIters = 0;
|
||
while ( QCoreApplication::hasPendingEvents() && ++nIters < 100 )
|
||
#endif
|
||
{
|
||
QCoreApplication::processEvents();
|
||
}
|
||
|
||
} );
|
||
connect( progressDialog.get(), &QProgressDialog::canceled, this, [ & ]
|
||
{
|
||
feedback->cancel();
|
||
} );
|
||
|
||
QgsApplication::taskManager()->addTask( proxyTask );
|
||
|
||
QString fileName = QDir( dir ).filePath( QStringLiteral( "atlas" ) ); // filename is overridden by atlas
|
||
QgsLayoutExporter::ExportResult result = QgsLayoutExporter::exportToImage( printAtlas, fileName, fileExt, settings, error, feedback.get() );
|
||
|
||
proxyTask->finalize( result == QgsLayoutExporter::Success );
|
||
|
||
cursorOverride.release();
|
||
|
||
switch ( result )
|
||
{
|
||
case QgsLayoutExporter::Success:
|
||
mMessageBar->pushMessage( tr( "Export atlas" ),
|
||
tr( "Successfully exported atlas to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( dir ).toString(), QDir::toNativeSeparators( dir ) ),
|
||
Qgis::Success, 0 );
|
||
break;
|
||
|
||
case QgsLayoutExporter::IteratorError:
|
||
QMessageBox::warning( this, tr( "Export Atlas as Image" ),
|
||
tr( "Error encountered while exporting atlas." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::PrintError:
|
||
case QgsLayoutExporter::SvgLayerError:
|
||
case QgsLayoutExporter::Canceled:
|
||
// no meaning for raster exports, will not be encountered
|
||
break;
|
||
|
||
case QgsLayoutExporter::FileError:
|
||
QMessageBox::warning( this, tr( "Export Atlas as Image" ),
|
||
error,
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::MemoryError:
|
||
QMessageBox::warning( this, tr( "Export Atlas as Image" ),
|
||
tr( "Trying to create image of %2×%3 @ %4dpi "
|
||
"resulted in a memory overflow.\n\n"
|
||
"Please try a lower resolution or a smaller paper size." )
|
||
.arg( imageSize.width() ).arg( imageSize.height() ).arg( settings.dpi ),
|
||
QMessageBox::Ok, QMessageBox::Ok );
|
||
break;
|
||
}
|
||
mView->setPaintingEnabled( true );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::exportAtlasToSvg()
|
||
{
|
||
QgsLayoutAtlas *printAtlas = atlas();
|
||
if ( !printAtlas || !printAtlas->enabled() )
|
||
return;
|
||
|
||
loadAtlasPredefinedScalesFromProject();
|
||
if ( containsWmsLayers() )
|
||
{
|
||
showWmsPrintingWarning();
|
||
}
|
||
|
||
showSvgExportWarning();
|
||
|
||
// else, it has an atlas to render, so a directory must first be selected
|
||
if ( printAtlas->filenameExpression().isEmpty() )
|
||
{
|
||
int res = QMessageBox::warning( nullptr, tr( "Export Atlas" ),
|
||
tr( "The filename expression is empty. A default one will be used instead." ),
|
||
QMessageBox::Ok | QMessageBox::Cancel,
|
||
QMessageBox::Ok );
|
||
if ( res == QMessageBox::Cancel )
|
||
{
|
||
return;
|
||
}
|
||
QString error;
|
||
printAtlas->setFilenameExpression( QStringLiteral( "'output_'||@atlas_featurenumber" ), error );
|
||
}
|
||
|
||
QString lastUsedDir = defaultExportPath();
|
||
|
||
QFileDialog dlg( this, tr( "Export Atlas to Directory" ) );
|
||
dlg.setFileMode( QFileDialog::Directory );
|
||
dlg.setOption( QFileDialog::ShowDirsOnly, true );
|
||
dlg.setDirectory( lastUsedDir );
|
||
if ( !dlg.exec() )
|
||
{
|
||
return;
|
||
}
|
||
|
||
#ifdef Q_OS_MAC
|
||
QgisApp::instance()->activateWindow();
|
||
this->raise();
|
||
#endif
|
||
|
||
const QStringList files = dlg.selectedFiles();
|
||
if ( files.empty() || files.at( 0 ).isEmpty() )
|
||
{
|
||
return;
|
||
}
|
||
QString dir = files.at( 0 );
|
||
if ( dir.isEmpty() )
|
||
{
|
||
return;
|
||
}
|
||
setLastExportPath( dir );
|
||
|
||
// test directory (if it exists and is writable)
|
||
if ( !QDir( dir ).exists() || !QFileInfo( dir ).isWritable() )
|
||
{
|
||
QMessageBox::warning( nullptr, tr( "Export Atlas" ),
|
||
tr( "Unable to write into the given output directory. Canceling." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
return;
|
||
}
|
||
|
||
QgsLayoutExporter::SvgExportSettings svgSettings;
|
||
if ( !getSvgExportSettings( svgSettings ) )
|
||
return;
|
||
|
||
mView->setPaintingEnabled( false );
|
||
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
|
||
|
||
QString error;
|
||
std::unique_ptr< QgsFeedback > feedback = qgis::make_unique< QgsFeedback >();
|
||
std::unique_ptr< QProgressDialog > progressDialog = qgis::make_unique< QProgressDialog >( tr( "Rendering maps…" ), tr( "Abort" ), 0, 100, this );
|
||
progressDialog->setWindowTitle( tr( "Exporting Atlas" ) );
|
||
|
||
QgsProxyProgressTask *proxyTask = new QgsProxyProgressTask( tr( "Exporting “%1”" ).arg( mMasterLayout->name() ) );
|
||
|
||
connect( feedback.get(), &QgsFeedback::progressChanged, this, [ & ]( double progress )
|
||
{
|
||
progressDialog->setValue( static_cast< int >( progress ) );
|
||
progressDialog->setLabelText( feedback->property( "progress" ).toString() ) ;
|
||
|
||
proxyTask->setProxyProgress( progress );
|
||
|
||
#ifdef Q_OS_LINUX
|
||
// For some reason on Windows hasPendingEvents() always return true,
|
||
// but one iteration is actually enough on Windows to get good interactivity
|
||
// whereas on Linux we must allow for far more iterations.
|
||
// For safety limit the number of iterations
|
||
int nIters = 0;
|
||
while ( QCoreApplication::hasPendingEvents() && ++nIters < 100 )
|
||
#endif
|
||
{
|
||
QCoreApplication::processEvents();
|
||
}
|
||
|
||
} );
|
||
connect( progressDialog.get(), &QProgressDialog::canceled, this, [ & ]
|
||
{
|
||
feedback->cancel();
|
||
} );
|
||
|
||
QgsApplication::taskManager()->addTask( proxyTask );
|
||
|
||
QString filename = QDir( dir ).filePath( QStringLiteral( "atlas" ) ); // filename is overridden by atlas
|
||
QgsLayoutExporter::ExportResult result = QgsLayoutExporter::exportToSvg( printAtlas, filename, svgSettings, error, feedback.get() );
|
||
|
||
proxyTask->finalize( result == QgsLayoutExporter::Success );
|
||
|
||
cursorOverride.release();
|
||
switch ( result )
|
||
{
|
||
case QgsLayoutExporter::Success:
|
||
{
|
||
mMessageBar->pushMessage( tr( "Export atlas" ),
|
||
tr( "Successfully exported atlas to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( dir ).toString(), QDir::toNativeSeparators( dir ) ),
|
||
Qgis::Success, 0 );
|
||
break;
|
||
}
|
||
|
||
case QgsLayoutExporter::FileError:
|
||
QMessageBox::warning( this, tr( "Export Atlas as SVG" ),
|
||
error, QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::SvgLayerError:
|
||
QMessageBox::warning( this, tr( "Export Atlas as SVG" ),
|
||
tr( "Cannot create layered SVG file." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::PrintError:
|
||
QMessageBox::warning( this, tr( "Export Atlas as SVG" ),
|
||
tr( "Could not create print device." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
|
||
case QgsLayoutExporter::MemoryError:
|
||
QMessageBox::warning( this, tr( "Export Atlas as SVG" ),
|
||
tr( "Exporting the SVG "
|
||
"resulted in a memory overflow.\n\n"
|
||
"Please try a lower resolution or a smaller paper size." ),
|
||
QMessageBox::Ok, QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::IteratorError:
|
||
QMessageBox::warning( this, tr( "Export Atlas as SVG" ),
|
||
tr( "Error encountered while exporting atlas." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::Canceled:
|
||
// no meaning here
|
||
break;
|
||
}
|
||
|
||
mView->setPaintingEnabled( true );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::exportAtlasToPdf()
|
||
{
|
||
QgsLayoutAtlas *printAtlas = atlas();
|
||
if ( !printAtlas || !printAtlas->enabled() )
|
||
return;
|
||
|
||
loadAtlasPredefinedScalesFromProject();
|
||
if ( containsWmsLayers() )
|
||
{
|
||
showWmsPrintingWarning();
|
||
}
|
||
|
||
if ( requiresRasterization() )
|
||
{
|
||
showRasterizationWarning();
|
||
}
|
||
|
||
if ( containsAdvancedEffects() && ( mLayout->customProperty( QStringLiteral( "forceVector" ), false ).toBool() ) )
|
||
{
|
||
showForceVectorWarning();
|
||
}
|
||
|
||
bool singleFile = mLayout->customProperty( QStringLiteral( "singleFile" ), true ).toBool();
|
||
|
||
QString outputFileName;
|
||
QString dir;
|
||
if ( singleFile )
|
||
{
|
||
const QString defaultPath = defaultExportPath();
|
||
outputFileName = defaultPath + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".pdf" );
|
||
|
||
#ifdef Q_OS_MAC
|
||
QgisApp::instance()->activateWindow();
|
||
this->raise();
|
||
#endif
|
||
outputFileName = QFileDialog::getSaveFileName(
|
||
this,
|
||
tr( "Export to PDF" ),
|
||
outputFileName,
|
||
tr( "PDF Format" ) + " (*.pdf *.PDF)" );
|
||
this->activateWindow();
|
||
if ( outputFileName.isEmpty() )
|
||
{
|
||
return;
|
||
}
|
||
|
||
if ( !outputFileName.endsWith( QLatin1String( ".pdf" ), Qt::CaseInsensitive ) )
|
||
{
|
||
outputFileName += QLatin1String( ".pdf" );
|
||
}
|
||
setLastExportPath( outputFileName );
|
||
}
|
||
else
|
||
{
|
||
if ( printAtlas->filenameExpression().isEmpty() )
|
||
{
|
||
int res = QMessageBox::warning( nullptr, tr( "Export Atlas as PDF" ),
|
||
tr( "The filename expression is empty. A default one will be used instead." ),
|
||
QMessageBox::Ok | QMessageBox::Cancel,
|
||
QMessageBox::Ok );
|
||
if ( res == QMessageBox::Cancel )
|
||
{
|
||
return;
|
||
}
|
||
QString error;
|
||
printAtlas->setFilenameExpression( QStringLiteral( "'output_'||@atlas_featurenumber" ), error );
|
||
}
|
||
|
||
|
||
const QString lastUsedDir = defaultExportPath();
|
||
|
||
QFileDialog dlg( this, tr( "Export Atlas to Directory" ) );
|
||
dlg.setFileMode( QFileDialog::Directory );
|
||
dlg.setOption( QFileDialog::ShowDirsOnly, true );
|
||
dlg.setDirectory( lastUsedDir );
|
||
if ( !dlg.exec() )
|
||
{
|
||
return;
|
||
}
|
||
|
||
#ifdef Q_OS_MAC
|
||
QgisApp::instance()->activateWindow();
|
||
this->raise();
|
||
#endif
|
||
|
||
const QStringList files = dlg.selectedFiles();
|
||
if ( files.empty() || files.at( 0 ).isEmpty() )
|
||
{
|
||
return;
|
||
}
|
||
dir = files.at( 0 );
|
||
if ( dir.isEmpty() )
|
||
{
|
||
return;
|
||
}
|
||
setLastExportPath( dir );
|
||
|
||
// test directory (if it exists and is writable)
|
||
if ( !QDir( dir ).exists() || !QFileInfo( dir ).isWritable() )
|
||
{
|
||
QMessageBox::warning( nullptr, tr( "Export Atlas as PDF" ),
|
||
tr( "Unable to write into the given output directory. Canceling." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
return;
|
||
}
|
||
|
||
outputFileName = QDir( dir ).filePath( QStringLiteral( "atlas" ) ); // filename is overridden by atlas
|
||
}
|
||
|
||
mView->setPaintingEnabled( false );
|
||
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
|
||
|
||
QgsLayoutExporter::PdfExportSettings pdfSettings;
|
||
if ( !getPdfExportSettings( pdfSettings ) )
|
||
return;
|
||
|
||
pdfSettings.rasterizeWholeImage = mLayout->customProperty( QStringLiteral( "rasterize" ), false ).toBool();
|
||
|
||
QString error;
|
||
std::unique_ptr< QgsFeedback > feedback = qgis::make_unique< QgsFeedback >();
|
||
std::unique_ptr< QProgressDialog > progressDialog = qgis::make_unique< QProgressDialog >( tr( "Rendering maps…" ), tr( "Abort" ), 0, 100, this );
|
||
|
||
QgsProxyProgressTask *proxyTask = new QgsProxyProgressTask( tr( "Exporting “%1”" ).arg( mMasterLayout->name() ) );
|
||
|
||
progressDialog->setWindowTitle( tr( "Exporting Atlas" ) );
|
||
connect( feedback.get(), &QgsFeedback::progressChanged, this, [ & ]( double progress )
|
||
{
|
||
progressDialog->setValue( static_cast< int >( progress ) );
|
||
progressDialog->setLabelText( feedback->property( "progress" ).toString() ) ;
|
||
|
||
proxyTask->setProxyProgress( progress );
|
||
|
||
#ifdef Q_OS_LINUX
|
||
// For some reason on Windows hasPendingEvents() always return true,
|
||
// but one iteration is actually enough on Windows to get good interactivity
|
||
// whereas on Linux we must allow for far more iterations.
|
||
// For safety limit the number of iterations
|
||
int nIters = 0;
|
||
while ( QCoreApplication::hasPendingEvents() && ++nIters < 100 )
|
||
#endif
|
||
{
|
||
QCoreApplication::processEvents();
|
||
}
|
||
|
||
} );
|
||
connect( progressDialog.get(), &QProgressDialog::canceled, this, [ & ]
|
||
{
|
||
feedback->cancel();
|
||
} );
|
||
|
||
QgsApplication::taskManager()->addTask( proxyTask );
|
||
|
||
QgsLayoutExporter::ExportResult result = QgsLayoutExporter::Success;
|
||
if ( singleFile )
|
||
{
|
||
result = QgsLayoutExporter::exportToPdf( printAtlas, outputFileName, pdfSettings, error, feedback.get() );
|
||
}
|
||
else
|
||
{
|
||
result = QgsLayoutExporter::exportToPdfs( printAtlas, outputFileName, pdfSettings, error, feedback.get() );
|
||
}
|
||
|
||
proxyTask->finalize( result == QgsLayoutExporter::Success );
|
||
|
||
cursorOverride.release();
|
||
switch ( result )
|
||
{
|
||
case QgsLayoutExporter::Success:
|
||
{
|
||
if ( singleFile )
|
||
{
|
||
mMessageBar->pushMessage( tr( "Export atlas" ),
|
||
tr( "Successfully exported atlas to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( outputFileName ).toString(), QDir::toNativeSeparators( outputFileName ) ),
|
||
Qgis::Success, 0 );
|
||
}
|
||
else
|
||
{
|
||
mMessageBar->pushMessage( tr( "Export atlas" ),
|
||
tr( "Successfully exported atlas to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( dir ).toString(), QDir::toNativeSeparators( dir ) ),
|
||
Qgis::Success, 0 );
|
||
}
|
||
break;
|
||
}
|
||
|
||
case QgsLayoutExporter::FileError:
|
||
QMessageBox::warning( this, tr( "Export Atlas as PDF" ),
|
||
error, QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::SvgLayerError:
|
||
// no meaning
|
||
break;
|
||
|
||
case QgsLayoutExporter::PrintError:
|
||
QMessageBox::warning( this, tr( "Export Atlas as PDF" ),
|
||
tr( "Could not create print device." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
|
||
case QgsLayoutExporter::MemoryError:
|
||
QMessageBox::warning( this, tr( "Export Atlas as PDF" ),
|
||
tr( "Exporting the PDF "
|
||
"resulted in a memory overflow.\n\n"
|
||
"Please try a lower resolution or a smaller paper size." ),
|
||
QMessageBox::Ok, QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::IteratorError:
|
||
QMessageBox::warning( this, tr( "Export Atlas as PDF" ),
|
||
tr( "Error encountered while exporting atlas" ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::Canceled:
|
||
// no meaning here
|
||
break;
|
||
}
|
||
|
||
mView->setPaintingEnabled( true );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::exportReportToRaster()
|
||
{
|
||
QString outputFileName = QgsFileUtils::stringToSafeFilename( mMasterLayout->name() );
|
||
|
||
QPair<QString, QString> fileNExt = QgsGuiUtils::getSaveAsImageName( this, tr( "Save Report As" ), outputFileName );
|
||
this->activateWindow();
|
||
|
||
if ( fileNExt.first.isEmpty() )
|
||
{
|
||
return;
|
||
}
|
||
|
||
setLastExportPath( fileNExt.first );
|
||
|
||
#ifdef Q_OS_MAC
|
||
QgisApp::instance()->activateWindow();
|
||
this->raise();
|
||
#endif
|
||
|
||
QgsLayoutExporter::ImageExportSettings settings;
|
||
QSize imageSize;
|
||
if ( !getRasterExportSettings( settings, imageSize ) )
|
||
return;
|
||
|
||
mView->setPaintingEnabled( false );
|
||
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
|
||
|
||
QString error;
|
||
std::unique_ptr< QgsFeedback > feedback = qgis::make_unique< QgsFeedback >();
|
||
std::unique_ptr< QProgressDialog > progressDialog = qgis::make_unique< QProgressDialog >( tr( "Rendering report…" ), tr( "Abort" ), 0, 0, this );
|
||
progressDialog->setWindowTitle( tr( "Exporting Report" ) );
|
||
|
||
QgsProxyProgressTask *proxyTask = new QgsProxyProgressTask( tr( "Exporting “%1”" ).arg( mMasterLayout->name() ) );
|
||
|
||
connect( feedback.get(), &QgsFeedback::progressChanged, this, [ & ]( double )
|
||
{
|
||
//progressDialog->setValue( progress );
|
||
progressDialog->setLabelText( feedback->property( "progress" ).toString() ) ;
|
||
|
||
#ifdef Q_OS_LINUX
|
||
// For some reason on Windows hasPendingEvents() always return true,
|
||
// but one iteration is actually enough on Windows to get good interactivity
|
||
// whereas on Linux we must allow for far more iterations.
|
||
// For safety limit the number of iterations
|
||
int nIters = 0;
|
||
while ( QCoreApplication::hasPendingEvents() && ++nIters < 100 )
|
||
#endif
|
||
{
|
||
QCoreApplication::processEvents();
|
||
}
|
||
|
||
} );
|
||
connect( progressDialog.get(), &QProgressDialog::canceled, this, [ & ]
|
||
{
|
||
feedback->cancel();
|
||
} );
|
||
|
||
QgsApplication::taskManager()->addTask( proxyTask );
|
||
|
||
QFileInfo fi( fileNExt.first );
|
||
QString dir = fi.path();
|
||
QString fileName = dir + '/' + fi.baseName();
|
||
QgsLayoutExporter::ExportResult result = QgsLayoutExporter::exportToImage( static_cast< QgsReport * >( mMasterLayout ), fileName, fileNExt.second, settings, error, feedback.get() );
|
||
|
||
proxyTask->finalize( result == QgsLayoutExporter::Success );
|
||
cursorOverride.release();
|
||
|
||
switch ( result )
|
||
{
|
||
case QgsLayoutExporter::Success:
|
||
mMessageBar->pushMessage( tr( "Export report" ),
|
||
tr( "Successfully exported report to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( dir ).toString(), QDir::toNativeSeparators( dir ) ),
|
||
Qgis::Success, 0 );
|
||
break;
|
||
|
||
case QgsLayoutExporter::IteratorError:
|
||
QMessageBox::warning( this, tr( "Export Report as Image" ),
|
||
tr( "Error encountered while exporting report" ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::PrintError:
|
||
case QgsLayoutExporter::SvgLayerError:
|
||
case QgsLayoutExporter::Canceled:
|
||
// no meaning for raster exports, will not be encountered
|
||
break;
|
||
|
||
case QgsLayoutExporter::FileError:
|
||
QMessageBox::warning( this, tr( "Export Report as Image" ),
|
||
error,
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::MemoryError:
|
||
QMessageBox::warning( this, tr( "Export Report as Image" ),
|
||
tr( "Trying to create image of %2×%3 @ %4dpi "
|
||
"resulted in a memory overflow.\n\n"
|
||
"Please try a lower resolution or a smaller paper size." )
|
||
.arg( imageSize.width() ).arg( imageSize.height() ).arg( settings.dpi ),
|
||
QMessageBox::Ok, QMessageBox::Ok );
|
||
break;
|
||
}
|
||
mView->setPaintingEnabled( true );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::exportReportToSvg()
|
||
{
|
||
showSvgExportWarning();
|
||
|
||
const QString defaultPath = defaultExportPath();
|
||
QString outputFileName = defaultPath + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".svg" );
|
||
|
||
outputFileName = QFileDialog::getSaveFileName(
|
||
this,
|
||
tr( "Export Report as SVG" ),
|
||
outputFileName,
|
||
tr( "SVG Format" ) + " (*.svg *.SVG)" );
|
||
this->activateWindow();
|
||
if ( outputFileName.isEmpty() )
|
||
{
|
||
return;
|
||
}
|
||
|
||
if ( !outputFileName.endsWith( QLatin1String( ".svg" ), Qt::CaseInsensitive ) )
|
||
{
|
||
outputFileName += QLatin1String( ".svg" );
|
||
}
|
||
#ifdef Q_OS_MAC
|
||
QgisApp::instance()->activateWindow();
|
||
this->raise();
|
||
#endif
|
||
setLastExportPath( outputFileName );
|
||
|
||
QgsLayoutExporter::SvgExportSettings svgSettings;
|
||
if ( !getSvgExportSettings( svgSettings ) )
|
||
return;
|
||
|
||
mView->setPaintingEnabled( false );
|
||
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
|
||
|
||
QString error;
|
||
std::unique_ptr< QgsFeedback > feedback = qgis::make_unique< QgsFeedback >();
|
||
std::unique_ptr< QProgressDialog > progressDialog = qgis::make_unique< QProgressDialog >( tr( "Rendering maps…" ), tr( "Abort" ), 0, 0, this );
|
||
progressDialog->setWindowTitle( tr( "Exporting Report" ) );
|
||
|
||
QgsProxyProgressTask *proxyTask = new QgsProxyProgressTask( tr( "Exporting “%1”" ).arg( mMasterLayout->name() ) );
|
||
|
||
connect( feedback.get(), &QgsFeedback::progressChanged, this, [ & ]( double )
|
||
{
|
||
//progressDialog->setValue( progress );
|
||
progressDialog->setLabelText( feedback->property( "progress" ).toString() ) ;
|
||
|
||
#ifdef Q_OS_LINUX
|
||
// For some reason on Windows hasPendingEvents() always return true,
|
||
// but one iteration is actually enough on Windows to get good interactivity
|
||
// whereas on Linux we must allow for far more iterations.
|
||
// For safety limit the number of iterations
|
||
int nIters = 0;
|
||
while ( QCoreApplication::hasPendingEvents() && ++nIters < 100 )
|
||
#endif
|
||
{
|
||
QCoreApplication::processEvents();
|
||
}
|
||
|
||
} );
|
||
connect( progressDialog.get(), &QProgressDialog::canceled, this, [ & ]
|
||
{
|
||
feedback->cancel();
|
||
} );
|
||
|
||
QgsApplication::taskManager()->addTask( proxyTask );
|
||
|
||
QFileInfo fi( outputFileName );
|
||
QString outFile = fi.path() + '/' + fi.baseName();
|
||
QString dir = fi.path();
|
||
QgsLayoutExporter::ExportResult result = QgsLayoutExporter::exportToSvg( static_cast< QgsReport * >( mMasterLayout ), outFile, svgSettings, error, feedback.get() );
|
||
|
||
proxyTask->finalize( result == QgsLayoutExporter::Success );
|
||
cursorOverride.release();
|
||
switch ( result )
|
||
{
|
||
case QgsLayoutExporter::Success:
|
||
{
|
||
mMessageBar->pushMessage( tr( "Export report" ),
|
||
tr( "Successfully exported report to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( dir ).toString(), QDir::toNativeSeparators( dir ) ),
|
||
Qgis::Success, 0 );
|
||
break;
|
||
}
|
||
|
||
case QgsLayoutExporter::FileError:
|
||
QMessageBox::warning( this, tr( "Export Report as SVG" ),
|
||
error, QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::SvgLayerError:
|
||
QMessageBox::warning( this, tr( "Export Report as SVG" ),
|
||
tr( "Cannot create layered SVG file." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::PrintError:
|
||
QMessageBox::warning( this, tr( "Export Report as SVG" ),
|
||
tr( "Could not create print device." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
|
||
case QgsLayoutExporter::MemoryError:
|
||
QMessageBox::warning( this, tr( "Export Report as SVG" ),
|
||
tr( "Exporting the SVG "
|
||
"resulted in a memory overflow.\n\n"
|
||
"Please try a lower resolution or a smaller paper size." ),
|
||
QMessageBox::Ok, QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::IteratorError:
|
||
QMessageBox::warning( this, tr( "Export Report as SVG" ),
|
||
tr( "Error encountered while exporting report." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::Canceled:
|
||
// no meaning here
|
||
break;
|
||
}
|
||
|
||
mView->setPaintingEnabled( true );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::exportReportToPdf()
|
||
{
|
||
const QString defaultPath = defaultExportPath();
|
||
|
||
QString outputFileName = defaultPath + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".pdf" );
|
||
|
||
#ifdef Q_OS_MAC
|
||
QgisApp::instance()->activateWindow();
|
||
this->raise();
|
||
#endif
|
||
outputFileName = QFileDialog::getSaveFileName(
|
||
this,
|
||
tr( "Export Report as PDF" ),
|
||
outputFileName,
|
||
tr( "PDF Format" ) + " (*.pdf *.PDF)" );
|
||
this->activateWindow();
|
||
if ( outputFileName.isEmpty() )
|
||
{
|
||
return;
|
||
}
|
||
|
||
if ( !outputFileName.endsWith( QLatin1String( ".pdf" ), Qt::CaseInsensitive ) )
|
||
{
|
||
outputFileName += QLatin1String( ".pdf" );
|
||
}
|
||
setLastExportPath( outputFileName );
|
||
|
||
mView->setPaintingEnabled( false );
|
||
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
|
||
|
||
bool rasterize = false;
|
||
if ( mLayout )
|
||
{
|
||
rasterize = mLayout->customProperty( QStringLiteral( "rasterize" ), false ).toBool();
|
||
}
|
||
QgsLayoutExporter::PdfExportSettings pdfSettings;
|
||
if ( !getPdfExportSettings( pdfSettings ) )
|
||
return;
|
||
|
||
pdfSettings.rasterizeWholeImage = rasterize;
|
||
|
||
QString error;
|
||
std::unique_ptr< QgsFeedback > feedback = qgis::make_unique< QgsFeedback >();
|
||
std::unique_ptr< QProgressDialog > progressDialog = qgis::make_unique< QProgressDialog >( tr( "Rendering maps…" ), tr( "Abort" ), 0, 0, this );
|
||
progressDialog->setWindowTitle( tr( "Exporting Report" ) );
|
||
|
||
QgsProxyProgressTask *proxyTask = new QgsProxyProgressTask( tr( "Exporting “%1”" ).arg( mMasterLayout->name() ) );
|
||
|
||
connect( feedback.get(), &QgsFeedback::progressChanged, this, [ & ]( double )
|
||
{
|
||
//progressDialog->setValue( progress );
|
||
progressDialog->setLabelText( feedback->property( "progress" ).toString() ) ;
|
||
|
||
#ifdef Q_OS_LINUX
|
||
// For some reason on Windows hasPendingEvents() always return true,
|
||
// but one iteration is actually enough on Windows to get good interactivity
|
||
// whereas on Linux we must allow for far more iterations.
|
||
// For safety limit the number of iterations
|
||
int nIters = 0;
|
||
while ( QCoreApplication::hasPendingEvents() && ++nIters < 100 )
|
||
#endif
|
||
{
|
||
QCoreApplication::processEvents();
|
||
}
|
||
|
||
} );
|
||
connect( progressDialog.get(), &QProgressDialog::canceled, this, [ & ]
|
||
{
|
||
feedback->cancel();
|
||
} );
|
||
|
||
QgsApplication::taskManager()->addTask( proxyTask );
|
||
|
||
QgsLayoutExporter::ExportResult result = QgsLayoutExporter::exportToPdf( static_cast< QgsReport * >( mMasterLayout ), outputFileName, pdfSettings, error, feedback.get() );
|
||
|
||
proxyTask->finalize( result == QgsLayoutExporter::Success );
|
||
cursorOverride.release();
|
||
|
||
switch ( result )
|
||
{
|
||
case QgsLayoutExporter::Success:
|
||
{
|
||
mMessageBar->pushMessage( tr( "Export report" ),
|
||
tr( "Successfully exported report to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( outputFileName ).toString(), QDir::toNativeSeparators( outputFileName ) ),
|
||
Qgis::Success, 0 );
|
||
break;
|
||
}
|
||
|
||
case QgsLayoutExporter::FileError:
|
||
QMessageBox::warning( this, tr( "Export Report as PDF" ),
|
||
error, QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::SvgLayerError:
|
||
// no meaning
|
||
break;
|
||
|
||
case QgsLayoutExporter::PrintError:
|
||
QMessageBox::warning( this, tr( "Export Report as PDF" ),
|
||
tr( "Could not create print device." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
|
||
case QgsLayoutExporter::MemoryError:
|
||
QMessageBox::warning( this, tr( "Export Report as PDF" ),
|
||
tr( "Exporting the PDF "
|
||
"resulted in a memory overflow.\n\n"
|
||
"Please try a lower resolution or a smaller paper size." ),
|
||
QMessageBox::Ok, QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::IteratorError:
|
||
QMessageBox::warning( this, tr( "Export Report as PDF" ),
|
||
tr( "Error encountered while exporting report." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::Canceled:
|
||
// no meaning here
|
||
break;
|
||
}
|
||
|
||
mView->setPaintingEnabled( true );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::printReport()
|
||
{
|
||
QPrintDialog printDialog( printer(), nullptr );
|
||
if ( printDialog.exec() != QDialog::Accepted )
|
||
{
|
||
return;
|
||
}
|
||
|
||
mView->setPaintingEnabled( false );
|
||
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
|
||
|
||
QgsLayoutExporter::PrintExportSettings printSettings;
|
||
if ( mLayout )
|
||
printSettings.rasterizeWholeImage = mLayout->customProperty( QStringLiteral( "rasterize" ), false ).toBool();
|
||
|
||
QString error;
|
||
std::unique_ptr< QgsFeedback > feedback = qgis::make_unique< QgsFeedback >();
|
||
std::unique_ptr< QProgressDialog > progressDialog = qgis::make_unique< QProgressDialog >( tr( "Printing maps…" ), tr( "Abort" ), 0, 0, this );
|
||
progressDialog->setWindowTitle( tr( "Printing Report" ) );
|
||
|
||
QgsProxyProgressTask *proxyTask = new QgsProxyProgressTask( tr( "Printing “%1”" ).arg( mMasterLayout->name() ) );
|
||
|
||
connect( feedback.get(), &QgsFeedback::progressChanged, this, [ & ]( double )
|
||
{
|
||
//progressDialog->setValue( progress );
|
||
progressDialog->setLabelText( feedback->property( "progress" ).toString() ) ;
|
||
|
||
#ifdef Q_OS_LINUX
|
||
// For some reason on Windows hasPendingEvents() always return true,
|
||
// but one iteration is actually enough on Windows to get good interactivity
|
||
// whereas on Linux we must allow for far more iterations.
|
||
// For safety limit the number of iterations
|
||
int nIters = 0;
|
||
while ( QCoreApplication::hasPendingEvents() && ++nIters < 100 )
|
||
#endif
|
||
{
|
||
QCoreApplication::processEvents();
|
||
}
|
||
|
||
} );
|
||
connect( progressDialog.get(), &QProgressDialog::canceled, this, [ & ]
|
||
{
|
||
feedback->cancel();
|
||
} );
|
||
|
||
QgsApplication::taskManager()->addTask( proxyTask );
|
||
|
||
QPrinter *p = printer();
|
||
QString printerName = p->printerName();
|
||
p->setDocName( mMasterLayout->name() );
|
||
QgsLayoutExporter::ExportResult result = QgsLayoutExporter::print( static_cast< QgsReport * >( mMasterLayout ), *p, printSettings, error, feedback.get() );
|
||
|
||
proxyTask->finalize( result == QgsLayoutExporter::Success );
|
||
|
||
switch ( result )
|
||
{
|
||
case QgsLayoutExporter::Success:
|
||
{
|
||
QString message;
|
||
if ( !printerName.isEmpty() )
|
||
{
|
||
message = tr( "Successfully printed report to %1." ).arg( printerName );
|
||
}
|
||
else
|
||
{
|
||
message = tr( "Successfully printed report." );
|
||
}
|
||
mMessageBar->pushMessage( tr( "Print report" ),
|
||
message,
|
||
Qgis::Success, 0 );
|
||
break;
|
||
}
|
||
|
||
case QgsLayoutExporter::PrintError:
|
||
{
|
||
QString message;
|
||
if ( !printerName.isEmpty() )
|
||
{
|
||
message = tr( "Could not create print device for %1." ).arg( printerName );
|
||
}
|
||
else
|
||
{
|
||
message = tr( "Could not create print device." );
|
||
}
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Print Report" ),
|
||
message,
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
}
|
||
|
||
case QgsLayoutExporter::MemoryError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Print Report" ),
|
||
tr( "Printing the report "
|
||
"resulted in a memory overflow.\n\n"
|
||
"Please try a lower resolution or a smaller paper size." ),
|
||
QMessageBox::Ok, QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::IteratorError:
|
||
cursorOverride.release();
|
||
QMessageBox::warning( this, tr( "Print Report" ),
|
||
tr( "Error encountered while printing report." ),
|
||
QMessageBox::Ok,
|
||
QMessageBox::Ok );
|
||
break;
|
||
|
||
case QgsLayoutExporter::FileError:
|
||
case QgsLayoutExporter::SvgLayerError:
|
||
case QgsLayoutExporter::Canceled:
|
||
// no meaning for PDF exports, will not be encountered
|
||
break;
|
||
}
|
||
|
||
mView->setPaintingEnabled( true );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::pageSetup()
|
||
{
|
||
if ( currentLayout() && currentLayout()->pageCollection()->pageCount() > 0 )
|
||
{
|
||
// get orientation from first page
|
||
QgsLayoutItemPage::Orientation orientation = currentLayout()->pageCollection()->page( 0 )->orientation();
|
||
//set printer page orientation
|
||
setPrinterPageOrientation( orientation );
|
||
}
|
||
|
||
QPageSetupDialog pageSetupDialog( printer(), this );
|
||
pageSetupDialog.exec();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::pageOrientationChanged()
|
||
{
|
||
mSetPageOrientation = false;
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::populateLayoutsMenu()
|
||
{
|
||
QgisApp::instance()->populateLayoutsMenu( mLayoutsMenu );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::paste()
|
||
{
|
||
QPointF pt = mView->mapFromGlobal( QCursor::pos() );
|
||
//TODO - use a better way of determining whether paste was triggered by keystroke
|
||
//or menu item
|
||
QList< QgsLayoutItem * > items;
|
||
if ( ( pt.x() < 0 ) || ( pt.y() < 0 ) )
|
||
{
|
||
//action likely triggered by menu, paste items in center of screen
|
||
items = mView->pasteItems( QgsLayoutView::PasteModeCenter );
|
||
}
|
||
else
|
||
{
|
||
//action likely triggered by keystroke, paste items at cursor position
|
||
items = mView->pasteItems( QgsLayoutView::PasteModeCursor );
|
||
}
|
||
|
||
whileBlocking( currentLayout() )->deselectAll();
|
||
selectItems( items );
|
||
|
||
//switch back to select tool so that pasted items can be moved/resized (#8958)
|
||
mView->setTool( mSelectTool );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::pasteInPlace()
|
||
{
|
||
QList< QgsLayoutItem * > items = mView->pasteItems( QgsLayoutView::PasteModeInPlace );
|
||
|
||
whileBlocking( currentLayout() )->deselectAll();
|
||
selectItems( items );
|
||
|
||
//switch back to select tool so that pasted items can be moved/resized (#8958)
|
||
mView->setTool( mSelectTool );
|
||
}
|
||
|
||
QgsLayoutView *QgsLayoutDesignerDialog::view()
|
||
{
|
||
return mView;
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::saveWindowState()
|
||
{
|
||
QgsSettings settings;
|
||
settings.setValue( QStringLiteral( "LayoutDesigner/geometry" ), saveGeometry(), QgsSettings::App );
|
||
// store the toolbar/dock widget settings using Qt settings API
|
||
settings.setValue( QStringLiteral( "LayoutDesigner/state" ), saveState(), QgsSettings::App );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::restoreWindowState()
|
||
{
|
||
// restore the toolbar and dock widgets positions using Qt settings API
|
||
QgsSettings settings;
|
||
|
||
if ( !restoreState( settings.value( QStringLiteral( "LayoutDesigner/state" ), QByteArray::fromRawData( reinterpret_cast< const char * >( defaultLayerDesignerUIstate ), sizeof defaultLayerDesignerUIstate ), QgsSettings::App ).toByteArray() ) )
|
||
{
|
||
QgsDebugMsg( QStringLiteral( "restore of layout UI state failed" ) );
|
||
}
|
||
// restore window geometry
|
||
if ( !restoreGeometry( settings.value( QStringLiteral( "LayoutDesigner/geometry" ), QgsSettings::App ).toByteArray() ) )
|
||
{
|
||
QgsDebugMsg( QStringLiteral( "restore of layout UI geometry failed" ) );
|
||
// default to 80% of screen size, at 10% from top left corner
|
||
resize( QDesktopWidget().availableGeometry( this ).size() * 0.8 );
|
||
QSize pos = QDesktopWidget().availableGeometry( this ).size() * 0.1;
|
||
move( pos.width(), pos.height() );
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::activateNewItemCreationTool( int id, bool nodeBasedItem )
|
||
{
|
||
if ( !nodeBasedItem )
|
||
{
|
||
mAddItemTool->setItemMetadataId( id );
|
||
if ( mView )
|
||
mView->setTool( mAddItemTool );
|
||
}
|
||
else
|
||
{
|
||
mAddNodeItemTool->setItemMetadataId( id );
|
||
if ( mView )
|
||
mView->setTool( mAddNodeItemTool );
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::createLayoutPropertiesWidget()
|
||
{
|
||
if ( !mLayout )
|
||
{
|
||
return;
|
||
}
|
||
|
||
// update layout based widgets
|
||
QgsLayoutPropertiesWidget *oldLayoutWidget = qobject_cast<QgsLayoutPropertiesWidget *>( mGeneralPropertiesStack->takeMainPanel() );
|
||
delete oldLayoutWidget;
|
||
QgsLayoutGuideWidget *oldGuideWidget = qobject_cast<QgsLayoutGuideWidget *>( mGuideStack->takeMainPanel() );
|
||
delete oldGuideWidget;
|
||
|
||
mLayoutPropertiesWidget = new QgsLayoutPropertiesWidget( mGeneralDock, mLayout );
|
||
mLayoutPropertiesWidget->setDockMode( true );
|
||
mGeneralPropertiesStack->setMainPanel( mLayoutPropertiesWidget );
|
||
|
||
QgsLayoutGuideWidget *guideWidget = new QgsLayoutGuideWidget( mGuideDock, mLayout, mView );
|
||
guideWidget->setDockMode( true );
|
||
mGuideStack->setMainPanel( guideWidget );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::createAtlasWidget()
|
||
{
|
||
QgsPrintLayout *printLayout = dynamic_cast< QgsPrintLayout * >( mMasterLayout );
|
||
if ( !printLayout )
|
||
return;
|
||
|
||
QgsLayoutAtlas *atlas = printLayout->atlas();
|
||
QgsLayoutAtlasWidget *atlasWidget = new QgsLayoutAtlasWidget( mAtlasDock, printLayout );
|
||
atlasWidget->setMessageBar( mMessageBar );
|
||
mAtlasDock->setWidget( atlasWidget );
|
||
|
||
mAtlasToolbar->show();
|
||
mPanelsMenu->addAction( mAtlasDock->toggleViewAction() );
|
||
|
||
connect( atlas, &QgsLayoutAtlas::messagePushed, mStatusBar, [ = ]( const QString & message )
|
||
{
|
||
mStatusBar->showMessage( message );
|
||
} );
|
||
connect( atlas, &QgsLayoutAtlas::toggled, this, &QgsLayoutDesignerDialog::toggleAtlasControls );
|
||
connect( atlas, &QgsLayoutAtlas::toggled, this, &QgsLayoutDesignerDialog::refreshLayout );
|
||
connect( atlas, &QgsLayoutAtlas::numberFeaturesChanged, this, &QgsLayoutDesignerDialog::updateAtlasPageComboBox );
|
||
connect( atlas, &QgsLayoutAtlas::featureChanged, this, &QgsLayoutDesignerDialog::atlasFeatureChanged );
|
||
toggleAtlasControls( atlas->enabled() && atlas->coverageLayer() );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::createReportWidget()
|
||
{
|
||
QgsReport *report = dynamic_cast< QgsReport * >( mMasterLayout );
|
||
QgsReportOrganizerWidget *reportWidget = new QgsReportOrganizerWidget( mReportDock, this, report );
|
||
reportWidget->setMessageBar( mMessageBar );
|
||
mReportDock->setWidget( reportWidget );
|
||
mReportToolbar->show();
|
||
mPanelsMenu->addAction( mReportDock->toggleViewAction() );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::initializeRegistry()
|
||
{
|
||
sInitializedRegistry = true;
|
||
auto createPageWidget = ( []( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
|
||
{
|
||
std::unique_ptr< QgsLayoutPagePropertiesWidget > newWidget = qgis::make_unique< QgsLayoutPagePropertiesWidget >( nullptr, item );
|
||
return newWidget.release();
|
||
} );
|
||
|
||
QgsGui::layoutItemGuiRegistry()->addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutPage, QObject::tr( "Page" ), QIcon(), createPageWidget, nullptr, QString(), false, QgsLayoutItemAbstractGuiMetadata::FlagNoCreationTools ) );
|
||
|
||
}
|
||
|
||
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::showSvgExportWarning()
|
||
{
|
||
QgsSettings settings;
|
||
|
||
bool displaySVGWarning = settings.value( QStringLiteral( "/UI/displaySVGWarning" ), true ).toBool();
|
||
|
||
if ( displaySVGWarning )
|
||
{
|
||
QgsMessageViewer m( this, QgsGuiUtils::ModalDialogFlags, false );
|
||
m.setWindowTitle( tr( "Export as SVG" ) );
|
||
m.setCheckBoxText( tr( "Don't show this message again" ) );
|
||
m.setCheckBoxState( Qt::Unchecked );
|
||
m.setCheckBoxVisible( true );
|
||
m.setCheckBoxQgsSettingsLabel( QStringLiteral( "/UI/displaySVGWarning" ) );
|
||
m.setMessageAsHtml( tr( "<p>The SVG export function in QGIS has several "
|
||
"problems due to bugs and deficiencies in the " )
|
||
+ tr( "underlying Qt SVG library. In particular, there are problems "
|
||
"with layers not being clipped to the map "
|
||
"bounding box.</p>" )
|
||
+ tr( "If you require a vector-based output file from "
|
||
"QGIS it is suggested that you try exporting "
|
||
"to PDF if the SVG output is not "
|
||
"satisfactory."
|
||
"</p>" ) );
|
||
m.exec();
|
||
}
|
||
}
|
||
|
||
bool QgsLayoutDesignerDialog::requiresRasterization() const
|
||
{
|
||
QList< QgsLayoutItem *> items;
|
||
mLayout->layoutItems( items );
|
||
|
||
for ( QgsLayoutItem *currentItem : qgis::as_const( items ) )
|
||
{
|
||
if ( currentItem->requiresRasterization() )
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool QgsLayoutDesignerDialog::containsAdvancedEffects() const
|
||
{
|
||
QList< QgsLayoutItem *> items;
|
||
mLayout->layoutItems( items );
|
||
|
||
for ( QgsLayoutItem *currentItem : qgis::as_const( items ) )
|
||
{
|
||
if ( currentItem->containsAdvancedEffects() )
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::showRasterizationWarning()
|
||
{
|
||
|
||
if ( mLayout->customProperty( QStringLiteral( "rasterize" ), false ).toBool() ||
|
||
mLayout->customProperty( QStringLiteral( "forceVector" ), false ).toBool() )
|
||
return;
|
||
|
||
QgsMessageViewer m( this, QgsGuiUtils::ModalDialogFlags, false );
|
||
m.setWindowTitle( tr( "Composition Effects" ) );
|
||
m.setMessage( tr( "Advanced composition effects such as blend modes or vector layer transparency are enabled in this layout, which cannot be printed as vectors. Printing as a raster is recommended." ), QgsMessageOutput::MessageText );
|
||
m.setCheckBoxText( tr( "Print as raster" ) );
|
||
m.setCheckBoxState( Qt::Checked );
|
||
m.setCheckBoxVisible( true );
|
||
m.showMessage( true );
|
||
|
||
mLayout->setCustomProperty( QStringLiteral( "rasterize" ), m.checkBoxState() == Qt::Checked );
|
||
//make sure print as raster checkbox is updated
|
||
mLayoutPropertiesWidget->updateGui();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::showForceVectorWarning()
|
||
{
|
||
QgsSettings settings;
|
||
if ( settings.value( QStringLiteral( "LayoutDesigner/hideForceVectorWarning" ), false, QgsSettings::App ).toBool() )
|
||
return;
|
||
|
||
QgsMessageViewer m( this, QgsGuiUtils::ModalDialogFlags, false );
|
||
m.setWindowTitle( tr( "Force Vector" ) );
|
||
m.setMessage( tr( "This layout has the \"Always export as vectors\" option enabled, but the layout contains effects such as blend modes or vector layer transparency, which cannot be printed as vectors. The generated file will differ from the layout contents." ), QgsMessageOutput::MessageText );
|
||
m.setCheckBoxText( tr( "Never show this message again" ) );
|
||
m.setCheckBoxState( Qt::Unchecked );
|
||
m.setCheckBoxVisible( true );
|
||
m.showMessage( true );
|
||
|
||
if ( m.checkBoxState() == Qt::Checked )
|
||
{
|
||
settings.setValue( QStringLiteral( "LayoutDesigner/hideForceVectorWarning" ), true, QgsSettings::App );
|
||
}
|
||
}
|
||
|
||
bool QgsLayoutDesignerDialog::showFileSizeWarning()
|
||
{
|
||
// Image size
|
||
double oneInchInLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) );
|
||
QSizeF maxPageSize = mLayout->pageCollection()->maximumPageSize();
|
||
int width = static_cast< int >( mLayout->renderContext().dpi() * maxPageSize.width() / oneInchInLayoutUnits );
|
||
int height = static_cast< int >( mLayout->renderContext().dpi() * maxPageSize.height() / oneInchInLayoutUnits );
|
||
int memuse = width * height * 3 / 1000000; // pixmap + image
|
||
QgsDebugMsg( QStringLiteral( "Image %1x%2" ).arg( width ).arg( height ) );
|
||
QgsDebugMsg( QStringLiteral( "memuse = %1" ).arg( memuse ) );
|
||
|
||
if ( memuse > 400 ) // about 4500x4500
|
||
{
|
||
int answer = QMessageBox::warning( this, 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 false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool QgsLayoutDesignerDialog::getRasterExportSettings( QgsLayoutExporter::ImageExportSettings &settings, QSize &imageSize )
|
||
{
|
||
QSizeF maxPageSize;
|
||
bool hasUniformPageSizes = false;
|
||
double dpi = 300;
|
||
bool cropToContents = false;
|
||
int marginTop = 0;
|
||
int marginRight = 0;
|
||
int marginBottom = 0;
|
||
int marginLeft = 0;
|
||
bool antialias = true;
|
||
|
||
// Image size
|
||
if ( mLayout )
|
||
{
|
||
maxPageSize = mLayout->pageCollection()->maximumPageSize();
|
||
hasUniformPageSizes = mLayout->pageCollection()->hasUniformPageSizes();
|
||
dpi = mLayout->renderContext().dpi();
|
||
|
||
//get some defaults from the composition
|
||
cropToContents = mLayout->customProperty( QStringLiteral( "imageCropToContents" ), false ).toBool();
|
||
marginTop = mLayout->customProperty( QStringLiteral( "imageCropMarginTop" ), 0 ).toInt();
|
||
marginRight = mLayout->customProperty( QStringLiteral( "imageCropMarginRight" ), 0 ).toInt();
|
||
marginBottom = mLayout->customProperty( QStringLiteral( "imageCropMarginBottom" ), 0 ).toInt();
|
||
marginLeft = mLayout->customProperty( QStringLiteral( "imageCropMarginLeft" ), 0 ).toInt();
|
||
antialias = mLayout->customProperty( QStringLiteral( "imageAntialias" ), true ).toBool();
|
||
}
|
||
|
||
QgsLayoutImageExportOptionsDialog imageDlg( this );
|
||
imageDlg.setImageSize( maxPageSize );
|
||
imageDlg.setResolution( dpi );
|
||
imageDlg.setCropToContents( cropToContents );
|
||
imageDlg.setCropMargins( marginTop, marginRight, marginBottom, marginLeft );
|
||
if ( mLayout )
|
||
imageDlg.setGenerateWorldFile( mLayout->customProperty( QStringLiteral( "exportWorldFile" ), false ).toBool() );
|
||
imageDlg.setAntialiasing( antialias );
|
||
|
||
if ( !imageDlg.exec() )
|
||
return false;
|
||
|
||
imageSize = QSize( imageDlg.imageWidth(), imageDlg.imageHeight() );
|
||
cropToContents = imageDlg.cropToContents();
|
||
imageDlg.getCropMargins( marginTop, marginRight, marginBottom, marginLeft );
|
||
if ( mLayout )
|
||
{
|
||
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 );
|
||
mLayout->setCustomProperty( QStringLiteral( "imageAntialias" ), imageDlg.antialiasing() );
|
||
}
|
||
|
||
settings.cropToContents = cropToContents;
|
||
settings.cropMargins = QgsMargins( marginLeft, marginTop, marginRight, marginBottom );
|
||
settings.dpi = imageDlg.resolution();
|
||
if ( hasUniformPageSizes )
|
||
{
|
||
settings.imageSize = imageSize;
|
||
}
|
||
settings.generateWorldFile = imageDlg.generateWorldFile();
|
||
settings.flags = QgsLayoutRenderContext::FlagUseAdvancedEffects;
|
||
if ( imageDlg.antialiasing() )
|
||
settings.flags |= QgsLayoutRenderContext::FlagAntialiasing;
|
||
|
||
return true;
|
||
}
|
||
|
||
bool QgsLayoutDesignerDialog::getSvgExportSettings( QgsLayoutExporter::SvgExportSettings &settings )
|
||
{
|
||
bool groupLayers = false;
|
||
QgsRenderContext::TextRenderFormat prevTextRenderFormat = mMasterLayout->layoutProject()->labelingEngineSettings().defaultTextRenderFormat();
|
||
bool clipToContent = false;
|
||
double marginTop = 0.0;
|
||
double marginRight = 0.0;
|
||
double marginBottom = 0.0;
|
||
double marginLeft = 0.0;
|
||
bool previousForceVector = false;
|
||
bool layersAsGroup = false;
|
||
bool cropToContents = false;
|
||
double topMargin = 0.0;
|
||
double rightMargin = 0.0;
|
||
double bottomMargin = 0.0;
|
||
double leftMargin = 0.0;
|
||
bool includeMetadata = true;
|
||
if ( mLayout )
|
||
{
|
||
mLayout->customProperty( QStringLiteral( "forceVector" ), false ).toBool();
|
||
layersAsGroup = mLayout->customProperty( QStringLiteral( "svgGroupLayers" ), false ).toBool();
|
||
cropToContents = mLayout->customProperty( QStringLiteral( "svgCropToContents" ), false ).toBool();
|
||
topMargin = mLayout->customProperty( QStringLiteral( "svgCropMarginTop" ), 0 ).toInt();
|
||
rightMargin = mLayout->customProperty( QStringLiteral( "svgCropMarginRight" ), 0 ).toInt();
|
||
bottomMargin = mLayout->customProperty( QStringLiteral( "svgCropMarginBottom" ), 0 ).toInt();
|
||
leftMargin = mLayout->customProperty( QStringLiteral( "svgCropMarginLeft" ), 0 ).toInt();
|
||
includeMetadata = mLayout->customProperty( QStringLiteral( "svgIncludeMetadata" ), 1 ).toBool();
|
||
const int prevLayoutSettingLabelsAsOutlines = mLayout->customProperty( QStringLiteral( "svgTextFormat" ), -1 ).toInt();
|
||
if ( prevLayoutSettingLabelsAsOutlines >= 0 )
|
||
{
|
||
// previous layout setting takes default over project setting
|
||
prevTextRenderFormat = static_cast< QgsRenderContext::TextRenderFormat >( prevLayoutSettingLabelsAsOutlines );
|
||
}
|
||
}
|
||
|
||
// open options dialog
|
||
QDialog dialog;
|
||
Ui::QgsSvgExportOptionsDialog options;
|
||
options.setupUi( &dialog );
|
||
|
||
options.mTextRenderFormatComboBox->addItem( tr( "Always Export Text as Paths (Recommended)" ), QgsRenderContext::TextFormatAlwaysOutlines );
|
||
options.mTextRenderFormatComboBox->addItem( tr( "Always Export Text as Text Objects" ), QgsRenderContext::TextFormatAlwaysText );
|
||
|
||
options.mTextRenderFormatComboBox->setCurrentIndex( options.mTextRenderFormatComboBox->findData( prevTextRenderFormat ) );
|
||
options.chkMapLayersAsGroup->setChecked( layersAsGroup );
|
||
options.mClipToContentGroupBox->setChecked( cropToContents );
|
||
options.mForceVectorCheckBox->setChecked( previousForceVector );
|
||
options.mTopMarginSpinBox->setValue( topMargin );
|
||
options.mRightMarginSpinBox->setValue( rightMargin );
|
||
options.mBottomMarginSpinBox->setValue( bottomMargin );
|
||
options.mLeftMarginSpinBox->setValue( leftMargin );
|
||
options.mIncludeMetadataCheckbox->setChecked( includeMetadata );
|
||
|
||
if ( dialog.exec() != QDialog::Accepted )
|
||
return false;
|
||
|
||
groupLayers = options.chkMapLayersAsGroup->isChecked();
|
||
clipToContent = options.mClipToContentGroupBox->isChecked();
|
||
marginTop = options.mTopMarginSpinBox->value();
|
||
marginRight = options.mRightMarginSpinBox->value();
|
||
marginBottom = options.mBottomMarginSpinBox->value();
|
||
marginLeft = options.mLeftMarginSpinBox->value();
|
||
includeMetadata = options.mIncludeMetadataCheckbox->isChecked();
|
||
QgsRenderContext::TextRenderFormat textRenderFormat = static_cast< QgsRenderContext::TextRenderFormat >( options.mTextRenderFormatComboBox->currentData().toInt() );
|
||
|
||
if ( mLayout )
|
||
{
|
||
//save dialog settings
|
||
mLayout->setCustomProperty( QStringLiteral( "svgGroupLayers" ), groupLayers );
|
||
mLayout->setCustomProperty( QStringLiteral( "svgCropToContents" ), clipToContent );
|
||
mLayout->setCustomProperty( QStringLiteral( "svgCropMarginTop" ), marginTop );
|
||
mLayout->setCustomProperty( QStringLiteral( "svgCropMarginRight" ), marginRight );
|
||
mLayout->setCustomProperty( QStringLiteral( "svgCropMarginBottom" ), marginBottom );
|
||
mLayout->setCustomProperty( QStringLiteral( "svgCropMarginLeft" ), marginLeft );
|
||
mLayout->setCustomProperty( QStringLiteral( "svgIncludeMetadata" ), includeMetadata ? 1 : 0 );
|
||
mLayout->setCustomProperty( QStringLiteral( "svgTextFormat" ), static_cast< int >( textRenderFormat ) );
|
||
}
|
||
|
||
settings.cropToContents = clipToContent;
|
||
settings.cropMargins = QgsMargins( marginLeft, marginTop, marginRight, marginBottom );
|
||
settings.forceVectorOutput = options.mForceVectorCheckBox->isChecked();
|
||
settings.exportAsLayers = groupLayers;
|
||
settings.exportMetadata = includeMetadata;
|
||
settings.textRenderFormat = textRenderFormat;
|
||
|
||
return true;
|
||
}
|
||
|
||
bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExportSettings &settings )
|
||
{
|
||
QgsRenderContext::TextRenderFormat prevTextRenderFormat = mMasterLayout->layoutProject()->labelingEngineSettings().defaultTextRenderFormat();
|
||
bool previousForceVector = false;
|
||
bool includeMetadata = true;
|
||
if ( mLayout )
|
||
{
|
||
mLayout->customProperty( QStringLiteral( "forceVector" ), false ).toBool();
|
||
includeMetadata = mLayout->customProperty( QStringLiteral( "pdfIncludeMetadata" ), 1 ).toBool();
|
||
const int prevLayoutSettingLabelsAsOutlines = mLayout->customProperty( QStringLiteral( "pdfTextFormat" ), -1 ).toInt();
|
||
if ( prevLayoutSettingLabelsAsOutlines >= 0 )
|
||
{
|
||
// previous layout setting takes default over project setting
|
||
prevTextRenderFormat = static_cast< QgsRenderContext::TextRenderFormat >( prevLayoutSettingLabelsAsOutlines );
|
||
}
|
||
}
|
||
|
||
// open options dialog
|
||
QDialog dialog;
|
||
Ui::QgsPdfExportOptionsDialog options;
|
||
options.setupUi( &dialog );
|
||
|
||
options.mTextRenderFormatComboBox->addItem( tr( "Always Export Text as Paths (Recommended)" ), QgsRenderContext::TextFormatAlwaysOutlines );
|
||
options.mTextRenderFormatComboBox->addItem( tr( "Always Export Text as Text Objects" ), QgsRenderContext::TextFormatAlwaysText );
|
||
|
||
options.mTextRenderFormatComboBox->setCurrentIndex( options.mTextRenderFormatComboBox->findData( prevTextRenderFormat ) );
|
||
options.mForceVectorCheckBox->setChecked( previousForceVector );
|
||
options.mIncludeMetadataCheckbox->setChecked( includeMetadata );
|
||
|
||
if ( dialog.exec() != QDialog::Accepted )
|
||
return false;
|
||
|
||
includeMetadata = options.mIncludeMetadataCheckbox->isChecked();
|
||
QgsRenderContext::TextRenderFormat textRenderFormat = static_cast< QgsRenderContext::TextRenderFormat >( options.mTextRenderFormatComboBox->currentData().toInt() );
|
||
|
||
if ( mLayout )
|
||
{
|
||
//save dialog settings
|
||
mLayout->setCustomProperty( QStringLiteral( "pdfIncludeMetadata" ), includeMetadata ? 1 : 0 );
|
||
mLayout->setCustomProperty( QStringLiteral( "pdfTextFormat" ), static_cast< int >( textRenderFormat ) );
|
||
}
|
||
|
||
settings.forceVectorOutput = options.mForceVectorCheckBox->isChecked();
|
||
settings.exportMetadata = includeMetadata;
|
||
settings.textRenderFormat = textRenderFormat;
|
||
|
||
return true;
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::toggleAtlasControls( bool atlasEnabled )
|
||
{
|
||
//preview defaults to unchecked
|
||
mActionAtlasPreview->blockSignals( true );
|
||
mActionAtlasPreview->setChecked( false );
|
||
mActionAtlasFirst->setEnabled( false );
|
||
mActionAtlasLast->setEnabled( false );
|
||
mActionAtlasNext->setEnabled( false );
|
||
mActionAtlasPrev->setEnabled( false );
|
||
mAtlasPageComboBox->setEnabled( false );
|
||
mActionAtlasPreview->blockSignals( false );
|
||
mActionAtlasPreview->setEnabled( atlasEnabled );
|
||
mActionPrintAtlas->setEnabled( atlasEnabled );
|
||
mActionExportAtlasAsImage->setEnabled( atlasEnabled );
|
||
mActionExportAtlasAsSVG->setEnabled( atlasEnabled );
|
||
mActionExportAtlasAsPDF->setEnabled( atlasEnabled );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::updateAtlasPageComboBox( int pageCount )
|
||
{
|
||
QgsPrintLayout *printLayout = qobject_cast< QgsPrintLayout * >( mLayout );
|
||
if ( !printLayout )
|
||
return;
|
||
|
||
QgsLayoutAtlas *atlas = printLayout->atlas();
|
||
mAtlasPageComboBox->blockSignals( true );
|
||
mAtlasPageComboBox->clear();
|
||
for ( int i = 1; i <= pageCount && i < 100000; ++i )
|
||
{
|
||
QString name = atlas->nameForPage( i - 1 );
|
||
QString fullName = ( !name.isEmpty() ? QStringLiteral( "%1: %2" ).arg( i ).arg( name ) : QString::number( i ) );
|
||
|
||
mAtlasPageComboBox->addItem( fullName, i );
|
||
mAtlasPageComboBox->setItemData( i - 1, name, Qt::UserRole + 1 );
|
||
mAtlasPageComboBox->setItemData( i - 1, fullName, Qt::UserRole + 2 );
|
||
}
|
||
mAtlasPageComboBox->blockSignals( false );
|
||
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::atlasFeatureChanged( const QgsFeature &feature )
|
||
{
|
||
//TODO - this should be disabled during an export
|
||
|
||
QgsPrintLayout *printLayout = qobject_cast< QgsPrintLayout *>( mLayout );
|
||
if ( !printLayout )
|
||
return;
|
||
|
||
QgsLayoutAtlas *atlas = printLayout->atlas();
|
||
|
||
mAtlasPageComboBox->blockSignals( true );
|
||
//prefer to set index of current atlas page, if combo box is showing enough page items
|
||
if ( atlas->currentFeatureNumber() < mAtlasPageComboBox->count() )
|
||
{
|
||
mAtlasPageComboBox->setCurrentIndex( atlas->currentFeatureNumber() );
|
||
}
|
||
else
|
||
{
|
||
//fallback to setting the combo text to the page number
|
||
mAtlasPageComboBox->setEditText( QString::number( atlas->currentFeatureNumber() + 1 ) );
|
||
}
|
||
mAtlasPageComboBox->blockSignals( false );
|
||
|
||
//update expression context variables in map canvas to allow for previewing atlas feature based rendering
|
||
QgsMapCanvas *mapCanvas = QgisApp::instance()->mapCanvas();
|
||
mapCanvas->expressionContextScope().addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "atlas_featurenumber" ), atlas->currentFeatureNumber() + 1, true ) );
|
||
mapCanvas->expressionContextScope().addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "atlas_pagename" ), atlas->nameForPage( atlas->currentFeatureNumber() ), true ) );
|
||
mapCanvas->expressionContextScope().addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "atlas_feature" ), QVariant::fromValue( feature ), true ) );
|
||
mapCanvas->expressionContextScope().addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "atlas_featureid" ), feature.id(), true ) );
|
||
mapCanvas->expressionContextScope().addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "atlas_geometry" ), QVariant::fromValue( feature.geometry() ), true ) );
|
||
mapCanvas->stopRendering();
|
||
mapCanvas->refreshAllLayers();
|
||
|
||
mView->setSectionLabel( atlas->nameForPage( atlas->currentFeatureNumber() ) );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::loadAtlasPredefinedScalesFromProject()
|
||
{
|
||
QVector<qreal> projectScales;
|
||
// first look at project's scales
|
||
QStringList scales( mLayout->project()->readListEntry( QStringLiteral( "Scales" ), QStringLiteral( "/ScalesList" ) ) );
|
||
bool hasProjectScales( mLayout->project()->readBoolEntry( QStringLiteral( "Scales" ), QStringLiteral( "/useProjectScales" ) ) );
|
||
if ( !hasProjectScales || scales.isEmpty() )
|
||
{
|
||
// default to global map tool scales
|
||
QgsSettings settings;
|
||
QString scalesStr( settings.value( QStringLiteral( "Map/scales" ), PROJECT_SCALES ).toString() );
|
||
scales = scalesStr.split( ',' );
|
||
}
|
||
|
||
for ( auto scaleIt = scales.constBegin(); scaleIt != scales.constEnd(); ++scaleIt )
|
||
{
|
||
QStringList parts( scaleIt->split( ':' ) );
|
||
if ( parts.size() == 2 )
|
||
{
|
||
projectScales.push_back( parts[1].toDouble() );
|
||
}
|
||
}
|
||
mLayout->reportContext().setPredefinedScales( projectScales );
|
||
}
|
||
|
||
QgsLayoutAtlas *QgsLayoutDesignerDialog::atlas()
|
||
{
|
||
QgsPrintLayout *layout = qobject_cast< QgsPrintLayout *>( mLayout );
|
||
if ( !layout )
|
||
return nullptr;
|
||
return layout->atlas();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::toggleActions( bool layoutAvailable )
|
||
{
|
||
mActionPan->setEnabled( layoutAvailable );
|
||
mActionZoomTool->setEnabled( layoutAvailable );
|
||
mActionSelectMoveItem->setEnabled( layoutAvailable );
|
||
mActionZoomAll->setEnabled( layoutAvailable );
|
||
mActionZoomIn->setEnabled( layoutAvailable );
|
||
mActionZoomOut->setEnabled( layoutAvailable );
|
||
mActionZoomActual->setEnabled( layoutAvailable );
|
||
mActionZoomToWidth->setEnabled( layoutAvailable );
|
||
mActionAddPages->setEnabled( layoutAvailable );
|
||
mActionShowGrid->setEnabled( layoutAvailable );
|
||
mActionSnapGrid->setEnabled( layoutAvailable );
|
||
mActionShowGuides->setEnabled( layoutAvailable );
|
||
mActionSnapGuides->setEnabled( layoutAvailable );
|
||
mActionClearGuides->setEnabled( layoutAvailable );
|
||
mActionLayoutProperties->setEnabled( layoutAvailable );
|
||
mActionShowBoxes->setEnabled( layoutAvailable );
|
||
mActionSmartGuides->setEnabled( layoutAvailable );
|
||
mActionDeselectAll->setEnabled( layoutAvailable );
|
||
mActionSelectAll->setEnabled( layoutAvailable );
|
||
mActionInvertSelection->setEnabled( layoutAvailable );
|
||
mActionSelectNextBelow->setEnabled( layoutAvailable );
|
||
mActionSelectNextAbove->setEnabled( layoutAvailable );
|
||
mActionLockItems->setEnabled( layoutAvailable );
|
||
mActionUnlockAll->setEnabled( layoutAvailable );
|
||
mActionRaiseItems->setEnabled( layoutAvailable );
|
||
mActionLowerItems->setEnabled( layoutAvailable );
|
||
mActionMoveItemsToTop->setEnabled( layoutAvailable );
|
||
mActionMoveItemsToBottom->setEnabled( layoutAvailable );
|
||
mActionAlignLeft->setEnabled( layoutAvailable );
|
||
mActionAlignHCenter->setEnabled( layoutAvailable );
|
||
mActionAlignRight->setEnabled( layoutAvailable );
|
||
mActionAlignTop->setEnabled( layoutAvailable );
|
||
mActionAlignVCenter->setEnabled( layoutAvailable );
|
||
mActionAlignBottom->setEnabled( layoutAvailable );
|
||
mActionDistributeLeft->setEnabled( layoutAvailable );
|
||
mActionDistributeHCenter->setEnabled( layoutAvailable );
|
||
mActionDistributeRight->setEnabled( layoutAvailable );
|
||
mActionDistributeTop->setEnabled( layoutAvailable );
|
||
mActionDistributeVCenter->setEnabled( layoutAvailable );
|
||
mActionDistributeBottom->setEnabled( layoutAvailable );
|
||
mActionResizeNarrowest->setEnabled( layoutAvailable );
|
||
mActionResizeWidest->setEnabled( layoutAvailable );
|
||
mActionResizeShortest->setEnabled( layoutAvailable );
|
||
mActionResizeTallest->setEnabled( layoutAvailable );
|
||
mActionDeleteSelection->setEnabled( layoutAvailable );
|
||
mActionResizeToSquare->setEnabled( layoutAvailable );
|
||
mActionShowPage->setEnabled( layoutAvailable );
|
||
mActionGroupItems->setEnabled( layoutAvailable );
|
||
mActionUngroupItems->setEnabled( layoutAvailable );
|
||
mActionRefreshView->setEnabled( layoutAvailable );
|
||
mActionEditNodesItem->setEnabled( layoutAvailable );
|
||
mActionMoveItemContent->setEnabled( layoutAvailable );
|
||
mActionPasteInPlace->setEnabled( layoutAvailable );
|
||
mActionSaveAsTemplate->setEnabled( layoutAvailable );
|
||
mActionLoadFromTemplate->setEnabled( layoutAvailable );
|
||
mActionExportAsImage->setEnabled( layoutAvailable );
|
||
mActionExportAsPDF->setEnabled( layoutAvailable );
|
||
mActionExportAsSVG->setEnabled( layoutAvailable );
|
||
mActionCut->setEnabled( layoutAvailable );
|
||
mActionCopy->setEnabled( layoutAvailable );
|
||
mActionPaste->setEnabled( layoutAvailable );
|
||
menuAlign_Items->setEnabled( layoutAvailable );
|
||
menu_Distribute_Items->setEnabled( layoutAvailable );
|
||
menuResize->setEnabled( layoutAvailable );
|
||
|
||
const QList<QAction *> itemActions = mToolsActionGroup->actions();
|
||
for ( QAction *action : itemActions )
|
||
{
|
||
action->setEnabled( layoutAvailable );
|
||
}
|
||
for ( auto it = mItemGroupSubmenus.constBegin(); it != mItemGroupSubmenus.constEnd(); ++it )
|
||
{
|
||
it.value()->setEnabled( layoutAvailable );
|
||
}
|
||
for ( auto it = mItemGroupToolButtons.constBegin(); it != mItemGroupToolButtons.constEnd(); ++it )
|
||
{
|
||
it.value()->setEnabled( layoutAvailable );
|
||
}
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::setPrinterPageOrientation( QgsLayoutItemPage::Orientation orientation )
|
||
{
|
||
if ( !mSetPageOrientation )
|
||
{
|
||
switch ( orientation )
|
||
{
|
||
case QgsLayoutItemPage::Landscape:
|
||
printer()->setOrientation( QPrinter::Landscape );
|
||
break;
|
||
|
||
case QgsLayoutItemPage::Portrait:
|
||
printer()->setOrientation( QPrinter::Portrait );
|
||
break;
|
||
}
|
||
|
||
mSetPageOrientation = true;
|
||
}
|
||
}
|
||
|
||
QPrinter *QgsLayoutDesignerDialog::printer()
|
||
{
|
||
//only create the printer on demand - creating a printer object can be very slow
|
||
//due to QTBUG-3033
|
||
if ( !mPrinter )
|
||
mPrinter = qgis::make_unique< QPrinter >();
|
||
|
||
return mPrinter.get();
|
||
}
|
||
|
||
QString QgsLayoutDesignerDialog::reportTypeString()
|
||
{
|
||
if ( atlas() )
|
||
return tr( "atlas" );
|
||
else
|
||
return tr( "report" );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::updateActionNames( QgsMasterLayoutInterface::Type type )
|
||
{
|
||
switch ( type )
|
||
{
|
||
case QgsMasterLayoutInterface::PrintLayout:
|
||
mActionDuplicateLayout->setText( tr( "&Duplicate Layout…" ) );
|
||
mActionDuplicateLayout->setToolTip( tr( "Duplicate layout" ) );
|
||
mActionDuplicateLayout->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionDuplicateLayout.svg" ) ) );
|
||
mActionRemoveLayout->setText( tr( "Delete Layout…" ) );
|
||
mActionRemoveLayout->setToolTip( tr( "Delete layout" ) );
|
||
mActionRenameLayout->setText( tr( "Rename Layout…" ) );
|
||
mActionRenameLayout->setToolTip( tr( "Rename layout" ) );
|
||
mActionNewLayout->setText( tr( "New Layout…" ) );
|
||
mActionNewLayout->setToolTip( tr( "New layout" ) );
|
||
mActionNewLayout->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionNewLayout.svg" ) ) );
|
||
break;
|
||
|
||
case QgsMasterLayoutInterface::Report:
|
||
mActionDuplicateLayout->setText( tr( "&Duplicate Report…" ) );
|
||
mActionDuplicateLayout->setToolTip( tr( "Duplicate report" ) );
|
||
mActionDuplicateLayout->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionDuplicateLayout.svg" ) ) );
|
||
mActionRemoveLayout->setText( tr( "Delete Report…" ) );
|
||
mActionRemoveLayout->setToolTip( tr( "Delete report" ) );
|
||
mActionRenameLayout->setText( tr( "Rename Report…" ) );
|
||
mActionRenameLayout->setToolTip( tr( "Rename report" ) );
|
||
mActionNewLayout->setText( tr( "New Report…" ) );
|
||
mActionNewLayout->setToolTip( tr( "New report" ) );
|
||
mActionNewLayout->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionNewReport.svg" ) ) );
|
||
break;
|
||
}
|
||
}
|
||
|
||
QString QgsLayoutDesignerDialog::defaultExportPath() const
|
||
{
|
||
// first priority - last export folder saved in project
|
||
const QString projectLastExportPath = QgsFileUtils::findClosestExistingPath( QgsProject::instance()->readEntry( QStringLiteral( "Layouts" ), QStringLiteral( "/lastLayoutExportDir" ), QString() ) );
|
||
if ( !projectLastExportPath.isEmpty() )
|
||
return projectLastExportPath;
|
||
|
||
// second priority - project home path
|
||
const QString projectHome = QgsFileUtils::findClosestExistingPath( QgsProject::instance()->homePath() );
|
||
if ( !projectHome.isEmpty() )
|
||
return projectHome;
|
||
|
||
// last priority - app setting last export folder, with homepath as backup
|
||
QgsSettings s;
|
||
return QgsFileUtils::findClosestExistingPath( s.value( QStringLiteral( "lastLayoutExportDir" ), QDir::homePath(), QgsSettings::App ).toString() );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::setLastExportPath( const QString &path ) const
|
||
{
|
||
QFileInfo fi( path );
|
||
QString savePath;
|
||
if ( fi.isFile() )
|
||
savePath = fi.path();
|
||
else
|
||
savePath = path;
|
||
|
||
QgsProject::instance()->writeEntry( QStringLiteral( "Layouts" ), QStringLiteral( "/lastLayoutExportDir" ), savePath );
|
||
QgsSettings().setValue( QStringLiteral( "lastLayoutExportDir" ), savePath, QgsSettings::App );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::updateWindowTitle()
|
||
{
|
||
QString title;
|
||
if ( mSectionTitle.isEmpty() )
|
||
title = mTitle;
|
||
else
|
||
title = QStringLiteral( "%1 - %2" ).arg( mTitle, mSectionTitle );
|
||
|
||
if ( QgsProject::instance()->isDirty() )
|
||
title.prepend( '*' );
|
||
|
||
setWindowTitle( title );
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::selectItems( const QList<QgsLayoutItem *> &items )
|
||
{
|
||
for ( QGraphicsItem *item : items )
|
||
{
|
||
if ( item )
|
||
{
|
||
item->setSelected( true );
|
||
}
|
||
}
|
||
|
||
//update item panel
|
||
const QList<QgsLayoutItem *> selectedItemList = currentLayout()->selectedLayoutItems();
|
||
if ( !selectedItemList.isEmpty() )
|
||
{
|
||
showItemOptions( selectedItemList.at( 0 ) );
|
||
}
|
||
else
|
||
{
|
||
showItemOptions( nullptr );
|
||
}
|
||
}
|
||
|
||
QgsMessageBar *QgsLayoutDesignerDialog::messageBar()
|
||
{
|
||
return mMessageBar;
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::setAtlasPreviewEnabled( bool enabled )
|
||
{
|
||
whileBlocking( mActionAtlasPreview )->setChecked( enabled );
|
||
atlasPreviewTriggered( enabled );
|
||
}
|
||
|
||
bool QgsLayoutDesignerDialog::atlasPreviewEnabled() const
|
||
{
|
||
return mActionAtlasPreview->isChecked();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::setAtlasFeature( QgsMapLayer *layer, const QgsFeature &feat )
|
||
{
|
||
QgsLayoutAtlas *layoutAtlas = atlas();
|
||
if ( !layoutAtlas || !layoutAtlas->enabled() || layoutAtlas->coverageLayer() != layer )
|
||
{
|
||
//either atlas isn't enabled, or layer doesn't match
|
||
return;
|
||
}
|
||
|
||
if ( !mActionAtlasPreview->isChecked() )
|
||
{
|
||
//update gui controls
|
||
whileBlocking( mActionAtlasPreview )->setChecked( true );
|
||
atlasPreviewTriggered( true );
|
||
}
|
||
|
||
//set current preview feature id
|
||
layoutAtlas->seekTo( feat );
|
||
|
||
//bring layout window to foreground
|
||
activate();
|
||
}
|
||
|
||
void QgsLayoutDesignerDialog::setSectionTitle( const QString &title )
|
||
{
|
||
mSectionTitle = title;
|
||
updateWindowTitle();
|
||
mView->setSectionLabel( title );
|
||
}
|
||
|
||
|