Skip to content

Commit 12dcfab

Browse files
committedJun 9, 2020
[geopdf] Allow users to reorder layers in the generated layer tree
Fixes #36535
1 parent 8ccd127 commit 12dcfab

File tree

8 files changed

+134
-31
lines changed

8 files changed

+134
-31
lines changed
 

‎src/app/layout/qgslayoutdesignerdialog.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4339,6 +4339,7 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
43394339
bool geoPdf = false;
43404340
bool useOgcBestPracticeFormat = false;
43414341
QStringList exportThemes;
4342+
QStringList geoPdfLayerOrder;
43424343
if ( mLayout )
43434344
{
43444345
settings.flags = mLayout->renderContext().flags();
@@ -4352,6 +4353,10 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
43524353
const QString themes = mLayout->customProperty( QStringLiteral( "pdfExportThemes" ) ).toString();
43534354
if ( !themes.isEmpty() )
43544355
exportThemes = themes.split( QStringLiteral( "~~~" ) );
4356+
const QString layerOrder = mLayout->customProperty( QStringLiteral( "pdfLayerOrder" ) ).toString();
4357+
if ( !layerOrder.isEmpty() )
4358+
geoPdfLayerOrder = layerOrder.split( QStringLiteral( "~~~" ) );
4359+
43554360
const int prevLayoutSettingLabelsAsOutlines = mLayout->customProperty( QStringLiteral( "pdfTextFormat" ), -1 ).toInt();
43564361
if ( prevLayoutSettingLabelsAsOutlines >= 0 )
43574362
{
@@ -4383,7 +4388,7 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
43834388
}
43844389
}
43854390

4386-
QgsLayoutPdfExportOptionsDialog dialog( this, allowGeoPdfExport, dialogGeoPdfReason );
4391+
QgsLayoutPdfExportOptionsDialog dialog( this, allowGeoPdfExport, dialogGeoPdfReason, geoPdfLayerOrder );
43874392

43884393
dialog.setTextRenderFormat( prevTextRenderFormat );
43894394
dialog.setForceVector( forceVector );
@@ -4408,6 +4413,7 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
44084413
geoPdf = dialog.exportGeoPdf();
44094414
useOgcBestPracticeFormat = dialog.useOgcBestPracticeFormat();
44104415
exportThemes = dialog.exportThemes();
4416+
geoPdfLayerOrder = dialog.geoPdfLayerOrder();
44114417

44124418
if ( mLayout )
44134419
{
@@ -4421,6 +4427,7 @@ bool QgsLayoutDesignerDialog::getPdfExportSettings( QgsLayoutExporter::PdfExport
44214427
mLayout->setCustomProperty( QStringLiteral( "pdfCreateGeoPdf" ), geoPdf ? 1 : 0 );
44224428
mLayout->setCustomProperty( QStringLiteral( "pdfOgcBestPracticeFormat" ), useOgcBestPracticeFormat ? 1 : 0 );
44234429
mLayout->setCustomProperty( QStringLiteral( "pdfExportThemes" ), exportThemes.join( QStringLiteral( "~~~" ) ) );
4430+
mLayout->setCustomProperty( QStringLiteral( "pdfLayerOrder" ), geoPdfLayerOrder.join( QStringLiteral( "~~~" ) ) );
44244431
}
44254432

44264433
settings.forceVectorOutput = forceVector;

‎src/core/layout/qgslayoutgeopdfexporter.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,25 @@ QgsLayoutGeoPdfExporter::QgsLayoutGeoPdfExporter( QgsLayout *layout )
145145
map->addRenderedFeatureHandler( handler );
146146
}
147147

148-
const QList< QgsMapLayer * > layerOrder = mLayout->project()->layerTreeRoot()->layerOrder();
148+
// start with project layer order, and then apply custom layer order if set
149+
QStringList geoPdfLayerOrder;
150+
const QString presetLayerOrder = mLayout->customProperty( QStringLiteral( "pdfLayerOrder" ) ).toString();
151+
if ( !presetLayerOrder.isEmpty() )
152+
geoPdfLayerOrder = presetLayerOrder.split( QStringLiteral( "~~~" ) );
153+
154+
QList< QgsMapLayer * > layerOrder = mLayout->project()->layerTreeRoot()->layerOrder();
155+
for ( auto it = geoPdfLayerOrder.rbegin(); it != geoPdfLayerOrder.rend(); ++it )
156+
{
157+
for ( int i = 0; i < layerOrder.size(); ++i )
158+
{
159+
if ( layerOrder.at( i )->id() == *it )
160+
{
161+
layerOrder.move( i, 0 );
162+
break;
163+
}
164+
}
165+
}
166+
149167
for ( const QgsMapLayer *layer : layerOrder )
150168
mLayerOrder << layer->id();
151169
}

‎src/gui/layout/qgsgeopdflayertreemodel.cpp

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
#include "qgsvectorlayer.h"
2323
#include "qgsapplication.h"
2424

25-
QgsGeoPdfLayerTreeModel::QgsGeoPdfLayerTreeModel( QgsLayerTree *rootNode, QObject *parent )
26-
: QgsLayerTreeModel( rootNode, parent )
25+
QgsGeoPdfLayerTreeModel::QgsGeoPdfLayerTreeModel( const QList<QgsMapLayer *> &layers, QObject *parent )
26+
: QgsMapLayerModel( layers, parent )
2727
{
28-
setFlags( nullptr ); // ideally we'd just show embedded legend nodes - but the api doesn't exist for this
28+
setItemsCanBeReordered( true );
2929
}
3030

3131
int QgsGeoPdfLayerTreeModel::columnCount( const QModelIndex &parent ) const
@@ -36,37 +36,36 @@ int QgsGeoPdfLayerTreeModel::columnCount( const QModelIndex &parent ) const
3636

3737
Qt::ItemFlags QgsGeoPdfLayerTreeModel::flags( const QModelIndex &idx ) const
3838
{
39+
if ( !idx.isValid() )
40+
return Qt::ItemIsDropEnabled;
41+
3942
if ( idx.column() == IncludeVectorAttributes )
4043
{
4144
if ( vectorLayer( idx ) )
42-
return QgsLayerTreeModel::flags( idx ) | Qt::ItemIsUserCheckable;
45+
return QgsMapLayerModel::flags( idx ) | Qt::ItemIsUserCheckable;
4346
else
44-
return QgsLayerTreeModel::flags( idx );
47+
return QgsMapLayerModel::flags( idx );
4548
}
4649

4750
if ( idx.column() == InitiallyVisible )
4851
{
49-
return QgsLayerTreeModel::flags( idx ) | Qt::ItemIsUserCheckable;
52+
return QgsMapLayerModel::flags( idx ) | Qt::ItemIsUserCheckable;
5053
}
5154

5255
if ( !mapLayer( idx ) )
5356
{
54-
return Qt::NoItemFlags;
57+
return nullptr;
5558
}
5659
else
5760
{
58-
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
61+
return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
5962
}
6063
return Qt::NoItemFlags;
6164
}
6265

6366
QgsMapLayer *QgsGeoPdfLayerTreeModel::mapLayer( const QModelIndex &idx ) const
6467
{
65-
QgsLayerTreeNode *node = index2node( index( idx.row(), LayerColumn, idx.parent() ) );
66-
if ( !node || !QgsLayerTree::isLayer( node ) )
67-
return nullptr;
68-
69-
return QgsLayerTree::toLayer( node )->layer();
68+
return layerFromIndex( index( idx.row(), LayerColumn, idx.parent() ) );
7069
}
7170

7271
QgsVectorLayer *QgsGeoPdfLayerTreeModel::vectorLayer( const QModelIndex &idx ) const
@@ -95,7 +94,7 @@ QVariant QgsGeoPdfLayerTreeModel::headerData( int section, Qt::Orientation orien
9594
}
9695
}
9796
}
98-
return QgsLayerTreeModel::headerData( section, orientation, role );
97+
return QgsMapLayerModel::headerData( section, orientation, role );
9998
}
10099

101100
QVariant QgsGeoPdfLayerTreeModel::data( const QModelIndex &idx, int role ) const
@@ -106,7 +105,7 @@ QVariant QgsGeoPdfLayerTreeModel::data( const QModelIndex &idx, int role ) const
106105
if ( role == Qt::CheckStateRole )
107106
return QVariant();
108107

109-
return QgsLayerTreeModel::data( idx, role );
108+
return QgsMapLayerModel::data( idx, role );
110109

111110
case GroupColumn:
112111
{
@@ -145,14 +144,13 @@ QVariant QgsGeoPdfLayerTreeModel::data( const QModelIndex &idx, int role ) const
145144
}
146145
return QVariant();
147146
}
148-
return QgsLayerTreeModel::data( idx, role );
147+
return QVariant();
149148
}
150149

151150
case IncludeVectorAttributes:
152151
{
153152
if ( role == Qt::CheckStateRole )
154153
{
155-
QgsLayerTreeNode *node = index2node( index( idx.row(), LayerColumn, idx.parent() ) );
156154
if ( QgsVectorLayer *vl = vectorLayer( idx ) )
157155
{
158156
const QVariant v = vl->customProperty( QStringLiteral( "geopdf/includeFeatures" ) );
@@ -162,8 +160,8 @@ QVariant QgsGeoPdfLayerTreeModel::data( const QModelIndex &idx, int role ) const
162160
}
163161
else
164162
{
165-
// otherwise, we default to the layer's visibility
166-
return node->itemVisibilityChecked() ? Qt::Checked : Qt::Unchecked;
163+
// otherwise, we default to true
164+
return Qt::Checked;
167165
}
168166
}
169167
return QVariant();
@@ -219,6 +217,9 @@ bool QgsGeoPdfLayerTreeModel::setData( const QModelIndex &index, const QVariant
219217
}
220218
break;
221219
}
220+
221+
case LayerColumn:
222+
return QgsMapLayerModel::setData( index, value, role );
222223
}
223224
return false;
224225
}
@@ -244,10 +245,10 @@ QgsGeoPdfLayerFilteredTreeModel::QgsGeoPdfLayerFilteredTreeModel( QgsGeoPdfLayer
244245

245246
bool QgsGeoPdfLayerFilteredTreeModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
246247
{
247-
if ( QgsLayerTreeNode *node = mLayerTreeModel->index2node( sourceModel()->index( source_row, 0, source_parent ) ) )
248+
if ( QgsMapLayer *layer = mLayerTreeModel->layerFromIndex( sourceModel()->index( source_row, 0, source_parent ) ) )
248249
{
249250
// filter out non-spatial layers
250-
if ( QgsLayerTree::isLayer( node ) && QgsLayerTree::toLayer( node ) && QgsLayerTree::toLayer( node )->layer() && !QgsLayerTree::toLayer( node )->layer()->isSpatial() )
251+
if ( !layer->isSpatial() )
251252
return false;
252253

253254
}

‎src/gui/layout/qgsgeopdflayertreemodel.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
#include <QItemDelegate>
2323

2424
#include "qgis_gui.h"
25-
#include "qgslayertreemodel.h"
25+
#include "qgsmaplayermodel.h"
2626

2727
class QgsMapCanvas;
2828
class QgsProject;
29+
class QgsVectorLayer;
2930

3031

3132
/**
@@ -35,7 +36,7 @@ class QgsProject;
3536
* \note This class is not a part of public API
3637
* \since QGIS 3.12
3738
*/
38-
class GUI_EXPORT QgsGeoPdfLayerTreeModel : public QgsLayerTreeModel
39+
class GUI_EXPORT QgsGeoPdfLayerTreeModel : public QgsMapLayerModel
3940
{
4041
Q_OBJECT
4142

@@ -51,7 +52,7 @@ class GUI_EXPORT QgsGeoPdfLayerTreeModel : public QgsLayerTreeModel
5152
};
5253

5354
//! constructor
54-
QgsGeoPdfLayerTreeModel( QgsLayerTree *rootNode, QObject *parent = nullptr );
55+
QgsGeoPdfLayerTreeModel( const QList< QgsMapLayer * > &layers, QObject *parent = nullptr );
5556

5657
int columnCount( const QModelIndex &parent ) const override;
5758
QVariant headerData( int section, Qt::Orientation orientation, int role ) const override;

‎src/gui/layout/qgslayoutpdfexportoptionsdialog.cpp

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@
2323
#include "qgsproject.h"
2424
#include "qgsmapthemecollection.h"
2525
#include "qgsgeopdflayertreemodel.h"
26+
#include "qgslayertree.h"
2627

2728
#include <QCheckBox>
2829
#include <QPushButton>
2930
#include <QMenu>
3031

31-
QgsLayoutPdfExportOptionsDialog::QgsLayoutPdfExportOptionsDialog( QWidget *parent, bool allowGeoPdfExport, const QString &geoPdfReason, Qt::WindowFlags flags )
32+
QgsLayoutPdfExportOptionsDialog::QgsLayoutPdfExportOptionsDialog( QWidget *parent, bool allowGeoPdfExport, const QString &geoPdfReason, const QStringList &geoPdfLayerOrder, Qt::WindowFlags flags )
3233
: QDialog( parent, flags )
3334
{
3435
setupUi( this );
@@ -67,12 +68,30 @@ QgsLayoutPdfExportOptionsDialog::QgsLayoutPdfExportOptionsDialog( QWidget *paren
6768
mThemesList->addItem( item );
6869
}
6970

70-
mGeoPdfStructureModel = new QgsGeoPdfLayerTreeModel( QgsProject::instance()->layerTreeRoot(), this );
71+
QList< QgsMapLayer * > order = QgsProject::instance()->layerTreeRoot()->layerOrder();
72+
for ( auto it = geoPdfLayerOrder.rbegin(); it != geoPdfLayerOrder.rend(); ++it )
73+
{
74+
for ( int i = 0; i < order.size(); ++i )
75+
{
76+
if ( order.at( i )->id() == *it )
77+
{
78+
order.move( i, 0 );
79+
break;
80+
}
81+
}
82+
}
83+
mGeoPdfStructureModel = new QgsGeoPdfLayerTreeModel( order, this );
7184
mGeoPdfStructureProxyModel = new QgsGeoPdfLayerFilteredTreeModel( mGeoPdfStructureModel, this );
7285
mGeoPdfStructureTree->setModel( mGeoPdfStructureProxyModel );
7386
mGeoPdfStructureTree->resizeColumnToContents( 0 );
7487
mGeoPdfStructureTree->header()->show();
75-
mGeoPdfStructureTree->setSelectionMode( QAbstractItemView::NoSelection );
88+
mGeoPdfStructureTree->setSelectionMode( QAbstractItemView::SingleSelection );
89+
mGeoPdfStructureTree->setSelectionBehavior( QAbstractItemView::SelectRows );
90+
91+
mGeoPdfStructureTree->setDragEnabled( true );
92+
mGeoPdfStructureTree->setAcceptDrops( true );
93+
mGeoPdfStructureTree->setDragDropMode( QAbstractItemView::InternalMove );
94+
mGeoPdfStructureTree->setDefaultDropAction( Qt::MoveAction );
7695

7796
mGeoPdfStructureTree->setContextMenuPolicy( Qt::CustomContextMenu );
7897
connect( mGeoPdfStructureTree, &QTreeView::customContextMenuRequested, this, [ = ]( const QPoint & point )
@@ -219,6 +238,16 @@ QStringList QgsLayoutPdfExportOptionsDialog::exportThemes() const
219238
return res;
220239
}
221240

241+
QStringList QgsLayoutPdfExportOptionsDialog::geoPdfLayerOrder() const
242+
{
243+
QStringList order;
244+
for ( int row = 0; row < mGeoPdfStructureProxyModel->rowCount(); ++row )
245+
{
246+
order << mGeoPdfStructureProxyModel->data( mGeoPdfStructureProxyModel->index( row, 0 ), QgsGeoPdfLayerTreeModel::LayerIdRole ).toString();
247+
}
248+
return order;
249+
}
250+
222251
void QgsLayoutPdfExportOptionsDialog::showHelp()
223252
{
224253
QgsHelp::openHelp( QStringLiteral( "print_composer/create_output.html" ) );

‎src/gui/layout/qgslayoutpdfexportoptionsdialog.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,14 @@ class GUI_EXPORT QgsLayoutPdfExportOptionsDialog: public QDialog, private Ui::Qg
4848
* \param parent parent widget
4949
* \param allowGeoPdfExport set to FALSE if geoPdf export is blocked
5050
* \param geoPdfReason set to a descriptive translated string explaining why geopdf export is not available if applicable
51+
* \param geoPdfLayerOrder optional layer ID order list for layers in the geopdf file. Any layers not present in this list
52+
* will instead be appended to the end of the geopdf layer list
5153
* \param flags window flags
5254
*/
5355
QgsLayoutPdfExportOptionsDialog( QWidget *parent = nullptr,
5456
bool allowGeoPdfExport = true,
5557
const QString &geoPdfReason = QString(),
58+
const QStringList &geoPdfLayerOrder = QStringList(),
5659
Qt::WindowFlags flags = nullptr );
5760

5861
//! Sets the text render format
@@ -97,6 +100,9 @@ class GUI_EXPORT QgsLayoutPdfExportOptionsDialog: public QDialog, private Ui::Qg
97100
//! Returns the list of export themes
98101
QStringList exportThemes() const;
99102

103+
//! Returns a list of map layer IDs in the desired order they should appear in a generated GeoPDF file
104+
QStringList geoPdfLayerOrder() const;
105+
100106
private slots:
101107

102108
void showHelp();

‎src/ui/layout/qgspdfexportoptions.ui

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
<x>0</x>
102102
<y>0</y>
103103
<width>451</width>
104-
<height>630</height>
104+
<height>612</height>
105105
</rect>
106106
</property>
107107
<layout class="QVBoxLayout" name="verticalLayout_6">
@@ -193,7 +193,7 @@
193193
<item>
194194
<widget class="QLabel" name="label_2">
195195
<property name="text">
196-
<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>
196+
<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>
197197
</property>
198198
<property name="wordWrap">
199199
<bool>true</bool>

‎tests/src/core/testqgslayoutgeopdfexport.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class TestQgsLayoutGeoPdfExport : public QObject
4343
void cleanup();// will be called after every testfunction.
4444
void testCollectingFeatures();
4545
void skipLayers();
46+
void layerOrder();
4647

4748
private:
4849

@@ -405,5 +406,45 @@ void TestQgsLayoutGeoPdfExport::skipLayers()
405406
QCOMPARE( polyFeatures.count(), 10 ); // should be features, layer did not have any setting set
406407
}
407408

409+
void TestQgsLayoutGeoPdfExport::layerOrder()
410+
{
411+
QgsVectorLayer *linesLayer = new QgsVectorLayer( TEST_DATA_DIR + QStringLiteral( "/lines.shp" ),
412+
QStringLiteral( "lines" ), QStringLiteral( "ogr" ) );
413+
QVERIFY( linesLayer->isValid() );
414+
QgsVectorLayer *pointsLayer = new QgsVectorLayer( TEST_DATA_DIR + QStringLiteral( "/points.shp" ),
415+
QStringLiteral( "points" ), QStringLiteral( "ogr" ) );
416+
QVERIFY( pointsLayer->isValid() );
417+
QgsVectorLayer *polygonLayer = new QgsVectorLayer( TEST_DATA_DIR + QStringLiteral( "/polys.shp" ),
418+
QStringLiteral( "polys" ), QStringLiteral( "ogr" ) );
419+
QVERIFY( polygonLayer->isValid() );
420+
pointsLayer->setDisplayExpression( QStringLiteral( "Staff" ) );
421+
422+
QgsProject p;
423+
p.addMapLayer( linesLayer );
424+
p.addMapLayer( pointsLayer );
425+
p.addMapLayer( polygonLayer );
426+
427+
QgsLayout l( &p );
428+
l.initializeDefaults();
429+
QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
430+
map->attemptSetSceneRect( QRectF( 20, 20, 200, 100 ) );
431+
map->setFrameEnabled( true );
432+
map->setLayers( QList<QgsMapLayer *>() << linesLayer << pointsLayer );
433+
map->setCrs( linesLayer->crs() );
434+
map->zoomToExtent( linesLayer->extent() );
435+
map->setBackgroundColor( QColor( 200, 220, 230 ) );
436+
map->setBackgroundEnabled( true );
437+
l.addLayoutItem( map );
438+
439+
QgsLayoutGeoPdfExporter geoPdfExporter( &l );
440+
// by default we should follow project layer order
441+
QCOMPARE( geoPdfExporter.layerOrder(), QStringList() << polygonLayer->id() << pointsLayer->id() << linesLayer->id() );
442+
443+
// but if a custom order is specified, respected that
444+
l.setCustomProperty( QStringLiteral( "pdfLayerOrder" ), QStringLiteral( "%1~~~%2" ).arg( linesLayer->id(), polygonLayer->id() ) );
445+
QgsLayoutGeoPdfExporter geoPdfExporter2( &l );
446+
QCOMPARE( geoPdfExporter2.layerOrder(), QStringList() << linesLayer->id() << polygonLayer->id() << pointsLayer->id() );
447+
}
448+
408449
QGSTEST_MAIN( TestQgsLayoutGeoPdfExport )
409450
#include "testqgslayoutgeopdfexport.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.