Skip to content

Commit

Permalink
[geopdf] Allow users to reorder layers in the generated layer tree
Browse files Browse the repository at this point in the history
Fixes #36535
  • Loading branch information
nyalldawson committed Jun 9, 2020
1 parent 8ccd127 commit 12dcfab
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 31 deletions.
9 changes: 8 additions & 1 deletion src/app/layout/qgslayoutdesignerdialog.cpp
Expand Up @@ -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();
Expand All @@ -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 )
{
Expand Down Expand Up @@ -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 );
Expand All @@ -4408,6 +4413,7 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
geoPdf = dialog.exportGeoPdf();
useOgcBestPracticeFormat = dialog.useOgcBestPracticeFormat();
exportThemes = dialog.exportThemes();
geoPdfLayerOrder = dialog.geoPdfLayerOrder();

if ( mLayout )
{
Expand All @@ -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;
Expand Down
20 changes: 19 additions & 1 deletion src/core/layout/qgslayoutgeopdfexporter.cpp
Expand Up @@ -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();
}
Expand Down
43 changes: 22 additions & 21 deletions src/gui/layout/qgsgeopdflayertreemodel.cpp
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:
{
Expand Down Expand Up @@ -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" ) );
Expand All @@ -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();
Expand Down Expand Up @@ -219,6 +217,9 @@ bool QgsGeoPdfLayerTreeModel::setData( const QModelIndex &index, const QVariant
}
break;
}

case LayerColumn:
return QgsMapLayerModel::setData( index, value, role );
}
return false;
}
Expand All @@ -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;

}
Expand Down
7 changes: 4 additions & 3 deletions src/gui/layout/qgsgeopdflayertreemodel.h
Expand Up @@ -22,10 +22,11 @@
#include <QItemDelegate>

#include "qgis_gui.h"
#include "qgslayertreemodel.h"
#include "qgsmaplayermodel.h"

class QgsMapCanvas;
class QgsProject;
class QgsVectorLayer;


/**
Expand All @@ -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

Expand All @@ -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;
Expand Down
35 changes: 32 additions & 3 deletions src/gui/layout/qgslayoutpdfexportoptionsdialog.cpp
Expand Up @@ -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 );
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -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" ) );
Expand Down
6 changes: 6 additions & 0 deletions src/gui/layout/qgslayoutpdfexportoptionsdialog.h
Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions src/ui/layout/qgspdfexportoptions.ui
Expand Up @@ -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">
Expand Down Expand Up @@ -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>
Expand Down
41 changes: 41 additions & 0 deletions tests/src/core/testqgslayoutgeopdfexport.cpp
Expand Up @@ -43,6 +43,7 @@ class TestQgsLayoutGeoPdfExport : public QObject
void cleanup();// will be called after every testfunction.
void testCollectingFeatures();
void skipLayers();
void layerOrder();

private:

Expand Down Expand Up @@ -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"

0 comments on commit 12dcfab

Please sign in to comment.