mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-06 00:03:16 -05:00
[geopdf] Allow users to reorder layers in the generated layer tree
Fixes #36535
This commit is contained in:
parent
8ccd127f4d
commit
12dcfabb26
@ -4339,6 +4339,7 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
|
||||
bool geoPdf = false;
|
||||
bool useOgcBestPracticeFormat = false;
|
||||
QStringList exportThemes;
|
||||
QStringList geoPdfLayerOrder;
|
||||
if ( mLayout )
|
||||
{
|
||||
settings.flags = mLayout->renderContext().flags();
|
||||
@ -4352,6 +4353,10 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
|
||||
const QString themes = mLayout->customProperty( QStringLiteral( "pdfExportThemes" ) ).toString();
|
||||
if ( !themes.isEmpty() )
|
||||
exportThemes = themes.split( QStringLiteral( "~~~" ) );
|
||||
const QString layerOrder = mLayout->customProperty( QStringLiteral( "pdfLayerOrder" ) ).toString();
|
||||
if ( !layerOrder.isEmpty() )
|
||||
geoPdfLayerOrder = layerOrder.split( QStringLiteral( "~~~" ) );
|
||||
|
||||
const int prevLayoutSettingLabelsAsOutlines = mLayout->customProperty( QStringLiteral( "pdfTextFormat" ), -1 ).toInt();
|
||||
if ( prevLayoutSettingLabelsAsOutlines >= 0 )
|
||||
{
|
||||
@ -4383,7 +4388,7 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
|
||||
}
|
||||
}
|
||||
|
||||
QgsLayoutPdfExportOptionsDialog dialog( this, allowGeoPdfExport, dialogGeoPdfReason );
|
||||
QgsLayoutPdfExportOptionsDialog dialog( this, allowGeoPdfExport, dialogGeoPdfReason, geoPdfLayerOrder );
|
||||
|
||||
dialog.setTextRenderFormat( prevTextRenderFormat );
|
||||
dialog.setForceVector( forceVector );
|
||||
@ -4408,6 +4413,7 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
|
||||
geoPdf = dialog.exportGeoPdf();
|
||||
useOgcBestPracticeFormat = dialog.useOgcBestPracticeFormat();
|
||||
exportThemes = dialog.exportThemes();
|
||||
geoPdfLayerOrder = dialog.geoPdfLayerOrder();
|
||||
|
||||
if ( mLayout )
|
||||
{
|
||||
@ -4421,6 +4427,7 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
|
||||
mLayout->setCustomProperty( QStringLiteral( "pdfCreateGeoPdf" ), geoPdf ? 1 : 0 );
|
||||
mLayout->setCustomProperty( QStringLiteral( "pdfOgcBestPracticeFormat" ), useOgcBestPracticeFormat ? 1 : 0 );
|
||||
mLayout->setCustomProperty( QStringLiteral( "pdfExportThemes" ), exportThemes.join( QStringLiteral( "~~~" ) ) );
|
||||
mLayout->setCustomProperty( QStringLiteral( "pdfLayerOrder" ), geoPdfLayerOrder.join( QStringLiteral( "~~~" ) ) );
|
||||
}
|
||||
|
||||
settings.forceVectorOutput = forceVector;
|
||||
|
||||
@ -145,7 +145,25 @@ QgsLayoutGeoPdfExporter::QgsLayoutGeoPdfExporter( QgsLayout *layout )
|
||||
map->addRenderedFeatureHandler( handler );
|
||||
}
|
||||
|
||||
const QList< QgsMapLayer * > layerOrder = mLayout->project()->layerTreeRoot()->layerOrder();
|
||||
// start with project layer order, and then apply custom layer order if set
|
||||
QStringList geoPdfLayerOrder;
|
||||
const QString presetLayerOrder = mLayout->customProperty( QStringLiteral( "pdfLayerOrder" ) ).toString();
|
||||
if ( !presetLayerOrder.isEmpty() )
|
||||
geoPdfLayerOrder = presetLayerOrder.split( QStringLiteral( "~~~" ) );
|
||||
|
||||
QList< QgsMapLayer * > layerOrder = mLayout->project()->layerTreeRoot()->layerOrder();
|
||||
for ( auto it = geoPdfLayerOrder.rbegin(); it != geoPdfLayerOrder.rend(); ++it )
|
||||
{
|
||||
for ( int i = 0; i < layerOrder.size(); ++i )
|
||||
{
|
||||
if ( layerOrder.at( i )->id() == *it )
|
||||
{
|
||||
layerOrder.move( i, 0 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ( const QgsMapLayer *layer : layerOrder )
|
||||
mLayerOrder << layer->id();
|
||||
}
|
||||
|
||||
@ -22,10 +22,10 @@
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgsapplication.h"
|
||||
|
||||
QgsGeoPdfLayerTreeModel::QgsGeoPdfLayerTreeModel( QgsLayerTree *rootNode, QObject *parent )
|
||||
: QgsLayerTreeModel( rootNode, parent )
|
||||
QgsGeoPdfLayerTreeModel::QgsGeoPdfLayerTreeModel( const QList<QgsMapLayer *> &layers, QObject *parent )
|
||||
: QgsMapLayerModel( layers, parent )
|
||||
{
|
||||
setFlags( nullptr ); // ideally we'd just show embedded legend nodes - but the api doesn't exist for this
|
||||
setItemsCanBeReordered( true );
|
||||
}
|
||||
|
||||
int QgsGeoPdfLayerTreeModel::columnCount( const QModelIndex &parent ) const
|
||||
@ -36,37 +36,36 @@ int QgsGeoPdfLayerTreeModel::columnCount( const QModelIndex &parent ) const
|
||||
|
||||
Qt::ItemFlags QgsGeoPdfLayerTreeModel::flags( const QModelIndex &idx ) const
|
||||
{
|
||||
if ( !idx.isValid() )
|
||||
return Qt::ItemIsDropEnabled;
|
||||
|
||||
if ( idx.column() == IncludeVectorAttributes )
|
||||
{
|
||||
if ( vectorLayer( idx ) )
|
||||
return QgsLayerTreeModel::flags( idx ) | Qt::ItemIsUserCheckable;
|
||||
return QgsMapLayerModel::flags( idx ) | Qt::ItemIsUserCheckable;
|
||||
else
|
||||
return QgsLayerTreeModel::flags( idx );
|
||||
return QgsMapLayerModel::flags( idx );
|
||||
}
|
||||
|
||||
if ( idx.column() == InitiallyVisible )
|
||||
{
|
||||
return QgsLayerTreeModel::flags( idx ) | Qt::ItemIsUserCheckable;
|
||||
return QgsMapLayerModel::flags( idx ) | Qt::ItemIsUserCheckable;
|
||||
}
|
||||
|
||||
if ( !mapLayer( idx ) )
|
||||
{
|
||||
return Qt::NoItemFlags;
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
|
||||
}
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
||||
QgsMapLayer *QgsGeoPdfLayerTreeModel::mapLayer( const QModelIndex &idx ) const
|
||||
{
|
||||
QgsLayerTreeNode *node = index2node( index( idx.row(), LayerColumn, idx.parent() ) );
|
||||
if ( !node || !QgsLayerTree::isLayer( node ) )
|
||||
return nullptr;
|
||||
|
||||
return QgsLayerTree::toLayer( node )->layer();
|
||||
return layerFromIndex( index( idx.row(), LayerColumn, idx.parent() ) );
|
||||
}
|
||||
|
||||
QgsVectorLayer *QgsGeoPdfLayerTreeModel::vectorLayer( const QModelIndex &idx ) const
|
||||
@ -95,7 +94,7 @@ QVariant QgsGeoPdfLayerTreeModel::headerData( int section, Qt::Orientation orien
|
||||
}
|
||||
}
|
||||
}
|
||||
return QgsLayerTreeModel::headerData( section, orientation, role );
|
||||
return QgsMapLayerModel::headerData( section, orientation, role );
|
||||
}
|
||||
|
||||
QVariant QgsGeoPdfLayerTreeModel::data( const QModelIndex &idx, int role ) const
|
||||
@ -106,7 +105,7 @@ QVariant QgsGeoPdfLayerTreeModel::data( const QModelIndex &idx, int role ) const
|
||||
if ( role == Qt::CheckStateRole )
|
||||
return QVariant();
|
||||
|
||||
return QgsLayerTreeModel::data( idx, role );
|
||||
return QgsMapLayerModel::data( idx, role );
|
||||
|
||||
case GroupColumn:
|
||||
{
|
||||
@ -145,14 +144,13 @@ QVariant QgsGeoPdfLayerTreeModel::data( const QModelIndex &idx, int role ) const
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
return QgsLayerTreeModel::data( idx, role );
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case IncludeVectorAttributes:
|
||||
{
|
||||
if ( role == Qt::CheckStateRole )
|
||||
{
|
||||
QgsLayerTreeNode *node = index2node( index( idx.row(), LayerColumn, idx.parent() ) );
|
||||
if ( QgsVectorLayer *vl = vectorLayer( idx ) )
|
||||
{
|
||||
const QVariant v = vl->customProperty( QStringLiteral( "geopdf/includeFeatures" ) );
|
||||
@ -162,8 +160,8 @@ QVariant QgsGeoPdfLayerTreeModel::data( const QModelIndex &idx, int role ) const
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise, we default to the layer's visibility
|
||||
return node->itemVisibilityChecked() ? Qt::Checked : Qt::Unchecked;
|
||||
// otherwise, we default to true
|
||||
return Qt::Checked;
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
@ -219,6 +217,9 @@ bool QgsGeoPdfLayerTreeModel::setData( const QModelIndex &index, const QVariant
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case LayerColumn:
|
||||
return QgsMapLayerModel::setData( index, value, role );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -244,10 +245,10 @@ QgsGeoPdfLayerFilteredTreeModel::QgsGeoPdfLayerFilteredTreeModel( QgsGeoPdfLayer
|
||||
|
||||
bool QgsGeoPdfLayerFilteredTreeModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
|
||||
{
|
||||
if ( QgsLayerTreeNode *node = mLayerTreeModel->index2node( sourceModel()->index( source_row, 0, source_parent ) ) )
|
||||
if ( QgsMapLayer *layer = mLayerTreeModel->layerFromIndex( sourceModel()->index( source_row, 0, source_parent ) ) )
|
||||
{
|
||||
// filter out non-spatial layers
|
||||
if ( QgsLayerTree::isLayer( node ) && QgsLayerTree::toLayer( node ) && QgsLayerTree::toLayer( node )->layer() && !QgsLayerTree::toLayer( node )->layer()->isSpatial() )
|
||||
if ( !layer->isSpatial() )
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@ -22,10 +22,11 @@
|
||||
#include <QItemDelegate>
|
||||
|
||||
#include "qgis_gui.h"
|
||||
#include "qgslayertreemodel.h"
|
||||
#include "qgsmaplayermodel.h"
|
||||
|
||||
class QgsMapCanvas;
|
||||
class QgsProject;
|
||||
class QgsVectorLayer;
|
||||
|
||||
|
||||
/**
|
||||
@ -35,7 +36,7 @@ class QgsProject;
|
||||
* \note This class is not a part of public API
|
||||
* \since QGIS 3.12
|
||||
*/
|
||||
class GUI_EXPORT QgsGeoPdfLayerTreeModel : public QgsLayerTreeModel
|
||||
class GUI_EXPORT QgsGeoPdfLayerTreeModel : public QgsMapLayerModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@ -51,7 +52,7 @@ class GUI_EXPORT QgsGeoPdfLayerTreeModel : public QgsLayerTreeModel
|
||||
};
|
||||
|
||||
//! constructor
|
||||
QgsGeoPdfLayerTreeModel( QgsLayerTree *rootNode, QObject *parent = nullptr );
|
||||
QgsGeoPdfLayerTreeModel( const QList< QgsMapLayer * > &layers, QObject *parent = nullptr );
|
||||
|
||||
int columnCount( const QModelIndex &parent ) const override;
|
||||
QVariant headerData( int section, Qt::Orientation orientation, int role ) const override;
|
||||
|
||||
@ -23,12 +23,13 @@
|
||||
#include "qgsproject.h"
|
||||
#include "qgsmapthemecollection.h"
|
||||
#include "qgsgeopdflayertreemodel.h"
|
||||
#include "qgslayertree.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QPushButton>
|
||||
#include <QMenu>
|
||||
|
||||
QgsLayoutPdfExportOptionsDialog::QgsLayoutPdfExportOptionsDialog( QWidget *parent, bool allowGeoPdfExport, const QString &geoPdfReason, Qt::WindowFlags flags )
|
||||
QgsLayoutPdfExportOptionsDialog::QgsLayoutPdfExportOptionsDialog( QWidget *parent, bool allowGeoPdfExport, const QString &geoPdfReason, const QStringList &geoPdfLayerOrder, Qt::WindowFlags flags )
|
||||
: QDialog( parent, flags )
|
||||
{
|
||||
setupUi( this );
|
||||
@ -67,12 +68,30 @@ QgsLayoutPdfExportOptionsDialog::QgsLayoutPdfExportOptionsDialog( QWidget *paren
|
||||
mThemesList->addItem( item );
|
||||
}
|
||||
|
||||
mGeoPdfStructureModel = new QgsGeoPdfLayerTreeModel( QgsProject::instance()->layerTreeRoot(), this );
|
||||
QList< QgsMapLayer * > order = QgsProject::instance()->layerTreeRoot()->layerOrder();
|
||||
for ( auto it = geoPdfLayerOrder.rbegin(); it != geoPdfLayerOrder.rend(); ++it )
|
||||
{
|
||||
for ( int i = 0; i < order.size(); ++i )
|
||||
{
|
||||
if ( order.at( i )->id() == *it )
|
||||
{
|
||||
order.move( i, 0 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mGeoPdfStructureModel = new QgsGeoPdfLayerTreeModel( order, this );
|
||||
mGeoPdfStructureProxyModel = new QgsGeoPdfLayerFilteredTreeModel( mGeoPdfStructureModel, this );
|
||||
mGeoPdfStructureTree->setModel( mGeoPdfStructureProxyModel );
|
||||
mGeoPdfStructureTree->resizeColumnToContents( 0 );
|
||||
mGeoPdfStructureTree->header()->show();
|
||||
mGeoPdfStructureTree->setSelectionMode( QAbstractItemView::NoSelection );
|
||||
mGeoPdfStructureTree->setSelectionMode( QAbstractItemView::SingleSelection );
|
||||
mGeoPdfStructureTree->setSelectionBehavior( QAbstractItemView::SelectRows );
|
||||
|
||||
mGeoPdfStructureTree->setDragEnabled( true );
|
||||
mGeoPdfStructureTree->setAcceptDrops( true );
|
||||
mGeoPdfStructureTree->setDragDropMode( QAbstractItemView::InternalMove );
|
||||
mGeoPdfStructureTree->setDefaultDropAction( Qt::MoveAction );
|
||||
|
||||
mGeoPdfStructureTree->setContextMenuPolicy( Qt::CustomContextMenu );
|
||||
connect( mGeoPdfStructureTree, &QTreeView::customContextMenuRequested, this, [ = ]( const QPoint & point )
|
||||
@ -219,6 +238,16 @@ QStringList QgsLayoutPdfExportOptionsDialog::exportThemes() const
|
||||
return res;
|
||||
}
|
||||
|
||||
QStringList QgsLayoutPdfExportOptionsDialog::geoPdfLayerOrder() const
|
||||
{
|
||||
QStringList order;
|
||||
for ( int row = 0; row < mGeoPdfStructureProxyModel->rowCount(); ++row )
|
||||
{
|
||||
order << mGeoPdfStructureProxyModel->data( mGeoPdfStructureProxyModel->index( row, 0 ), QgsGeoPdfLayerTreeModel::LayerIdRole ).toString();
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
void QgsLayoutPdfExportOptionsDialog::showHelp()
|
||||
{
|
||||
QgsHelp::openHelp( QStringLiteral( "print_composer/create_output.html" ) );
|
||||
|
||||
@ -48,11 +48,14 @@ class GUI_EXPORT QgsLayoutPdfExportOptionsDialog: public QDialog, private Ui::Qg
|
||||
* \param parent parent widget
|
||||
* \param allowGeoPdfExport set to FALSE if geoPdf export is blocked
|
||||
* \param geoPdfReason set to a descriptive translated string explaining why geopdf export is not available if applicable
|
||||
* \param geoPdfLayerOrder optional layer ID order list for layers in the geopdf file. Any layers not present in this list
|
||||
* will instead be appended to the end of the geopdf layer list
|
||||
* \param flags window flags
|
||||
*/
|
||||
QgsLayoutPdfExportOptionsDialog( QWidget *parent = nullptr,
|
||||
bool allowGeoPdfExport = true,
|
||||
const QString &geoPdfReason = QString(),
|
||||
const QStringList &geoPdfLayerOrder = QStringList(),
|
||||
Qt::WindowFlags flags = nullptr );
|
||||
|
||||
//! Sets the text render format
|
||||
@ -97,6 +100,9 @@ class GUI_EXPORT QgsLayoutPdfExportOptionsDialog: public QDialog, private Ui::Qg
|
||||
//! Returns the list of export themes
|
||||
QStringList exportThemes() const;
|
||||
|
||||
//! Returns a list of map layer IDs in the desired order they should appear in a generated GeoPDF file
|
||||
QStringList geoPdfLayerOrder() const;
|
||||
|
||||
private slots:
|
||||
|
||||
void showHelp();
|
||||
|
||||
@ -101,7 +101,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>451</width>
|
||||
<height>630</height>
|
||||
<height>612</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
@ -193,7 +193,7 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Uncheck layers to avoid exporting vector feature information for those layers, and optionally set the group name to allow multiple layers to be joined into a single logical PDF group</string>
|
||||
<string>Uncheck layers to avoid exporting vector feature information for those layers, and optionally set the group name to allow multiple layers to be joined into a single logical PDF group. Layers can be dragged and dropped to rearrange their order in the generated GeoPDF table of contents.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
|
||||
@ -43,6 +43,7 @@ class TestQgsLayoutGeoPdfExport : public QObject
|
||||
void cleanup();// will be called after every testfunction.
|
||||
void testCollectingFeatures();
|
||||
void skipLayers();
|
||||
void layerOrder();
|
||||
|
||||
private:
|
||||
|
||||
@ -405,5 +406,45 @@ void TestQgsLayoutGeoPdfExport::skipLayers()
|
||||
QCOMPARE( polyFeatures.count(), 10 ); // should be features, layer did not have any setting set
|
||||
}
|
||||
|
||||
void TestQgsLayoutGeoPdfExport::layerOrder()
|
||||
{
|
||||
QgsVectorLayer *linesLayer = new QgsVectorLayer( TEST_DATA_DIR + QStringLiteral( "/lines.shp" ),
|
||||
QStringLiteral( "lines" ), QStringLiteral( "ogr" ) );
|
||||
QVERIFY( linesLayer->isValid() );
|
||||
QgsVectorLayer *pointsLayer = new QgsVectorLayer( TEST_DATA_DIR + QStringLiteral( "/points.shp" ),
|
||||
QStringLiteral( "points" ), QStringLiteral( "ogr" ) );
|
||||
QVERIFY( pointsLayer->isValid() );
|
||||
QgsVectorLayer *polygonLayer = new QgsVectorLayer( TEST_DATA_DIR + QStringLiteral( "/polys.shp" ),
|
||||
QStringLiteral( "polys" ), QStringLiteral( "ogr" ) );
|
||||
QVERIFY( polygonLayer->isValid() );
|
||||
pointsLayer->setDisplayExpression( QStringLiteral( "Staff" ) );
|
||||
|
||||
QgsProject p;
|
||||
p.addMapLayer( linesLayer );
|
||||
p.addMapLayer( pointsLayer );
|
||||
p.addMapLayer( polygonLayer );
|
||||
|
||||
QgsLayout l( &p );
|
||||
l.initializeDefaults();
|
||||
QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
|
||||
map->attemptSetSceneRect( QRectF( 20, 20, 200, 100 ) );
|
||||
map->setFrameEnabled( true );
|
||||
map->setLayers( QList<QgsMapLayer *>() << linesLayer << pointsLayer );
|
||||
map->setCrs( linesLayer->crs() );
|
||||
map->zoomToExtent( linesLayer->extent() );
|
||||
map->setBackgroundColor( QColor( 200, 220, 230 ) );
|
||||
map->setBackgroundEnabled( true );
|
||||
l.addLayoutItem( map );
|
||||
|
||||
QgsLayoutGeoPdfExporter geoPdfExporter( &l );
|
||||
// by default we should follow project layer order
|
||||
QCOMPARE( geoPdfExporter.layerOrder(), QStringList() << polygonLayer->id() << pointsLayer->id() << linesLayer->id() );
|
||||
|
||||
// but if a custom order is specified, respected that
|
||||
l.setCustomProperty( QStringLiteral( "pdfLayerOrder" ), QStringLiteral( "%1~~~%2" ).arg( linesLayer->id(), polygonLayer->id() ) );
|
||||
QgsLayoutGeoPdfExporter geoPdfExporter2( &l );
|
||||
QCOMPARE( geoPdfExporter2.layerOrder(), QStringList() << linesLayer->id() << polygonLayer->id() << pointsLayer->id() );
|
||||
}
|
||||
|
||||
QGSTEST_MAIN( TestQgsLayoutGeoPdfExport )
|
||||
#include "testqgslayoutgeopdfexport.moc"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user