QGIS API Documentation 3.99.0-Master (a26b91b364d)
qgslayoutatlaswidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutatlaswidget.cpp
3 -----------------------------
4 begin : October 2012
5 copyright : (C) 2012 Hugo Mercier
6 email : hugo dot mercier at oslandia dot com
7 ***************************************************************************/
8/***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17#include <QComboBox>
18#include <QImageWriter>
19
21#include "moc_qgslayoutatlaswidget.cpp"
22#include "qgsprintlayout.h"
23#include "qgslayoutatlas.h"
25#include "qgslayoutundostack.h"
27#include "qgsmessagebar.h"
29
31 : QWidget( parent )
32 , mLayout( layout )
33 , mAtlas( layout->atlas() )
34{
35 setupUi( this );
36 connect( mUseAtlasCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutAtlasWidget::mUseAtlasCheckBox_stateChanged );
37 connect( mAtlasFilenamePatternEdit, &QLineEdit::editingFinished, this, &QgsLayoutAtlasWidget::mAtlasFilenamePatternEdit_editingFinished );
38 connect( mAtlasFilenameExpressionButton, &QToolButton::clicked, this, &QgsLayoutAtlasWidget::mAtlasFilenameExpressionButton_clicked );
39 connect( mAtlasLimitCoverageLayerRenderCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutAtlasWidget::mAtlasLimitCoverageLayerRenderCheckBox_stateChanged );
40 connect( mAtlasHideCoverageCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutAtlasWidget::mAtlasHideCoverageCheckBox_stateChanged );
41 connect( mAtlasSingleFileCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutAtlasWidget::mAtlasSingleFileCheckBox_stateChanged );
42 connect( mAtlasSortFeatureCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutAtlasWidget::mAtlasSortFeatureCheckBox_stateChanged );
43 connect( mAtlasSortFeatureDirectionButton, &QToolButton::clicked, this, &QgsLayoutAtlasWidget::mAtlasSortFeatureDirectionButton_clicked );
44 connect( mAtlasFeatureFilterEdit, &QLineEdit::editingFinished, this, &QgsLayoutAtlasWidget::mAtlasFeatureFilterEdit_editingFinished );
45 connect( mAtlasFeatureFilterButton, &QToolButton::clicked, this, &QgsLayoutAtlasWidget::mAtlasFeatureFilterButton_clicked );
46 connect( mAtlasFeatureFilterCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutAtlasWidget::mAtlasFeatureFilterCheckBox_stateChanged );
47
48 mAtlasCoverageLayerComboBox->setFilters( Qgis::LayerFilter::VectorLayer );
49
50 connect( mAtlasCoverageLayerComboBox, &QgsMapLayerComboBox::layerChanged, mAtlasSortExpressionWidget, &QgsFieldExpressionWidget::setLayer );
51 connect( mAtlasCoverageLayerComboBox, &QgsMapLayerComboBox::layerChanged, mPageNameWidget, &QgsFieldExpressionWidget::setLayer );
52 connect( mAtlasCoverageLayerComboBox, &QgsMapLayerComboBox::layerChanged, this, &QgsLayoutAtlasWidget::changeCoverageLayer );
53 connect( mAtlasSortExpressionWidget, static_cast<void ( QgsFieldExpressionWidget::* )( const QString &, bool )>( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsLayoutAtlasWidget::changesSortFeatureExpression );
54 connect( mPageNameWidget, static_cast<void ( QgsFieldExpressionWidget::* )( const QString &, bool )>( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsLayoutAtlasWidget::pageNameExpressionChanged );
55
56 // Sort direction
57 mAtlasSortFeatureDirectionButton->setEnabled( false );
58 mAtlasSortExpressionWidget->setEnabled( false );
59
60 // connect to updates
61 connect( mAtlas, &QgsLayoutAtlas::changed, this, &QgsLayoutAtlasWidget::updateGuiElements );
62
63 mPageNameWidget->registerExpressionContextGenerator( mLayout );
64
65 const QList<QByteArray> formats = QImageWriter::supportedImageFormats();
66 for ( int i = 0; i < formats.size(); ++i )
67 {
68 mAtlasFileFormat->addItem( QString( formats.at( i ) ) );
69 }
70 connect( mAtlasFileFormat, qOverload<int>( &QComboBox::currentIndexChanged ), this, [this]( int ) { changeFileFormat(); } );
71
72 updateGuiElements();
73}
74
76{
77 mMessageBar = bar;
78}
79
80void QgsLayoutAtlasWidget::mUseAtlasCheckBox_stateChanged( int state )
81{
82 if ( state == Qt::Checked )
83 {
84 mAtlas->setEnabled( true );
85 mConfigurationGroup->setEnabled( true );
86 mOutputGroup->setEnabled( true );
87 }
88 else
89 {
90 mAtlas->setEnabled( false );
91 mConfigurationGroup->setEnabled( false );
92 mOutputGroup->setEnabled( false );
93 }
94}
95
96void QgsLayoutAtlasWidget::changeCoverageLayer( QgsMapLayer *layer )
97{
98 if ( !mLayout )
99 return;
100
101 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
102
103 const QString prevPageNameExpression = mAtlas->pageNameExpression();
104 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Layer" ) );
105 mLayout->reportContext().setLayer( vl );
106 if ( !vl )
107 {
108 mAtlas->setCoverageLayer( nullptr );
109 }
110 else
111 {
112 mAtlas->setCoverageLayer( vl );
113 updateAtlasFeatures();
114 }
115
116 // if page name expression is still valid, retain it. Otherwise switch to a nice default.
117 QgsExpression exp( prevPageNameExpression );
119 if ( exp.prepare( &context ) && !exp.hasParserError() )
120 {
121 mAtlas->setPageNameExpression( prevPageNameExpression );
122 }
123 else if ( vl )
124 {
126 }
127
128 mLayout->undoStack()->endCommand();
129}
130
131void QgsLayoutAtlasWidget::mAtlasFilenamePatternEdit_editingFinished()
132{
133 if ( !mLayout )
134 return;
135
136 QString error;
137 mBlockUpdates = true;
138 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filename" ) );
139 if ( !mAtlas->setFilenameExpression( mAtlasFilenamePatternEdit->text(), error ) )
140 {
141 //expression could not be set
142 mMessageBar->pushWarning( tr( "Atlas" ), tr( "Could not set filename expression to '%1'.\nParser error:\n%2" ).arg( mAtlasFilenamePatternEdit->text(), error ) );
143 }
144 mLayout->undoStack()->endCommand();
145 mBlockUpdates = false;
146}
147
148void QgsLayoutAtlasWidget::mAtlasFilenameExpressionButton_clicked()
149{
150 if ( !mLayout || !mAtlas || !mAtlas->coverageLayer() )
151 {
152 return;
153 }
154
155 const QgsExpressionContext context = mLayout->createExpressionContext();
156 QgsExpressionBuilderDialog exprDlg( mAtlas->coverageLayer(), mAtlasFilenamePatternEdit->text(), this, QStringLiteral( "generic" ), context );
157 exprDlg.setWindowTitle( tr( "Expression Based Filename" ) );
158
159 if ( exprDlg.exec() == QDialog::Accepted )
160 {
161 const QString expression = exprDlg.expressionText();
162 if ( !expression.isEmpty() )
163 {
164 //set atlas filename expression
165 mAtlasFilenamePatternEdit->setText( expression );
166 QString error;
167 mBlockUpdates = true;
168 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filename" ) );
169 if ( !mAtlas->setFilenameExpression( expression, error ) )
170 {
171 //expression could not be set
172 mMessageBar->pushWarning( tr( "Atlas" ), tr( "Could not set filename expression to '%1'.\nParser error:\n%2" ).arg( expression, error ) );
173 }
174 mBlockUpdates = false;
175 mLayout->undoStack()->endCommand();
176 }
177 }
178}
179
180void QgsLayoutAtlasWidget::mAtlasLimitCoverageLayerRenderCheckBox_stateChanged( int state )
181{
182 if ( !mLayout )
183 return;
184
185 mBlockUpdates = true;
186 mLayout->undoStack()->beginCommand( mAtlas, tr( "Toggle Limit Atlas Layer Rendering to Current Feature" ) );
187 mAtlas->setLimitCoverageLayerRenderToCurrentFeature( state == Qt::Checked );
188 mLayout->undoStack()->endCommand();
189 mBlockUpdates = false;
190}
191
192void QgsLayoutAtlasWidget::mAtlasHideCoverageCheckBox_stateChanged( int state )
193{
194 if ( !mLayout )
195 return;
196
197 mBlockUpdates = true;
198 mLayout->undoStack()->beginCommand( mAtlas, tr( "Toggle Atlas Layer Visibility" ) );
199 mAtlas->setHideCoverage( state == Qt::Checked );
200 mLayout->undoStack()->endCommand();
201 mBlockUpdates = false;
202
203 mAtlasLimitCoverageLayerRenderCheckBox->setEnabled( state != Qt::Checked );
204}
205
206void QgsLayoutAtlasWidget::mAtlasSingleFileCheckBox_stateChanged( int state )
207{
208 if ( !mLayout )
209 return;
210
211 if ( state == Qt::Checked )
212 {
213 mAtlasFilenamePatternEdit->setEnabled( false );
214 mAtlasFilenameExpressionButton->setEnabled( false );
215 }
216 else
217 {
218 mAtlasFilenamePatternEdit->setEnabled( true );
219 mAtlasFilenameExpressionButton->setEnabled( true );
220 }
221
222 mLayout->setCustomProperty( QStringLiteral( "singleFile" ), state == Qt::Checked );
223}
224
225void QgsLayoutAtlasWidget::mAtlasSortFeatureCheckBox_stateChanged( int state )
226{
227 if ( !mLayout )
228 return;
229
230 if ( state == Qt::Checked )
231 {
232 mAtlasSortFeatureDirectionButton->setEnabled( true );
233 mAtlasSortExpressionWidget->setEnabled( true );
234 }
235 else
236 {
237 mAtlasSortFeatureDirectionButton->setEnabled( false );
238 mAtlasSortExpressionWidget->setEnabled( false );
239 }
240 mBlockUpdates = true;
241 mLayout->undoStack()->beginCommand( mAtlas, tr( "Toggle Atlas Sorting" ) );
242 mAtlas->setSortFeatures( state == Qt::Checked );
243 mLayout->undoStack()->endCommand();
244 mBlockUpdates = false;
245 updateAtlasFeatures();
246}
247
248void QgsLayoutAtlasWidget::changesSortFeatureExpression( const QString &expression, bool )
249{
250 if ( !mLayout )
251 return;
252
253 mBlockUpdates = true;
254 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Sort" ) );
255 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mAtlasCoverageLayerComboBox->currentLayer() );
256 mAtlas->setSortExpression( QgsExpression::quoteFieldExpression( expression, vlayer ) );
257 mLayout->undoStack()->endCommand();
258 mBlockUpdates = false;
259 updateAtlasFeatures();
260}
261
262void QgsLayoutAtlasWidget::updateAtlasFeatures()
263{
264 const bool updated = mAtlas->updateFeatures();
265 if ( !updated )
266 {
267 mMessageBar->pushInfo( tr( "Atlas" ), tr( "No matching atlas features found!" ) );
268
269 //Perhaps atlas preview should be disabled now? If so, it may get annoying if user is editing
270 //the filter expression and it keeps disabling itself.
271 }
272}
273
274void QgsLayoutAtlasWidget::mAtlasFeatureFilterCheckBox_stateChanged( int state )
275{
276 if ( !mLayout )
277 return;
278
279 if ( state == Qt::Checked )
280 {
281 mAtlasFeatureFilterEdit->setEnabled( true );
282 mAtlasFeatureFilterButton->setEnabled( true );
283 }
284 else
285 {
286 mAtlasFeatureFilterEdit->setEnabled( false );
287 mAtlasFeatureFilterButton->setEnabled( false );
288 }
289 mBlockUpdates = true;
290 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filter" ) );
291 mAtlas->setFilterFeatures( state == Qt::Checked );
292 mLayout->undoStack()->endCommand();
293 mBlockUpdates = false;
294 updateAtlasFeatures();
295}
296
297void QgsLayoutAtlasWidget::pageNameExpressionChanged( const QString &, bool valid )
298{
299 if ( !mLayout )
300 return;
301
302 const QString expression = mPageNameWidget->asExpression();
303 if ( !valid && !expression.isEmpty() )
304 {
305 return;
306 }
307
308 mBlockUpdates = true;
309 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Name" ) );
310 mAtlas->setPageNameExpression( expression );
311 mLayout->undoStack()->endCommand();
312 mBlockUpdates = false;
313}
314
315void QgsLayoutAtlasWidget::mAtlasFeatureFilterEdit_editingFinished()
316{
317 if ( !mLayout )
318 return;
319
320 QString error;
321 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filter" ) );
322
323 mBlockUpdates = true;
324 if ( !mAtlas->setFilterExpression( mAtlasFeatureFilterEdit->text(), error ) )
325 {
326 //expression could not be set
327 mMessageBar->pushWarning( tr( "Atlas" ), tr( "Could not set filter expression to '%1'.\nParser error:\n%2" ).arg( mAtlasFeatureFilterEdit->text(), error ) );
328 }
329 mBlockUpdates = false;
330 mLayout->undoStack()->endCommand();
331 updateAtlasFeatures();
332}
333
334void QgsLayoutAtlasWidget::mAtlasFeatureFilterButton_clicked()
335{
336 if ( !mLayout )
337 return;
338
339 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mAtlasCoverageLayerComboBox->currentLayer() );
340
341 if ( !vl )
342 {
343 return;
344 }
345
346 const QgsExpressionContext context = mLayout->createExpressionContext();
347 QgsExpressionBuilderDialog exprDlg( vl, mAtlasFeatureFilterEdit->text(), this, QStringLiteral( "generic" ), context );
348 exprDlg.setWindowTitle( tr( "Expression Based Filter" ) );
349
350 if ( exprDlg.exec() == QDialog::Accepted )
351 {
352 const QString expression = exprDlg.expressionText();
353 if ( !expression.isEmpty() )
354 {
355 mAtlasFeatureFilterEdit->setText( expression );
356 QString error;
357 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filter" ) );
358 mBlockUpdates = true;
359 if ( !mAtlas->setFilterExpression( mAtlasFeatureFilterEdit->text(), error ) )
360 {
361 //expression could not be set
362 mMessageBar->pushWarning( tr( "Atlas" ), tr( "Could not set filter expression to '%1'.\nParser error:\n%2" ).arg( mAtlasFeatureFilterEdit->text(), error ) );
363 }
364 mBlockUpdates = false;
365 mLayout->undoStack()->endCommand();
366 updateAtlasFeatures();
367 }
368 }
369}
370
371void QgsLayoutAtlasWidget::mAtlasSortFeatureDirectionButton_clicked()
372{
373 if ( !mLayout )
374 return;
375
376 Qt::ArrowType at = mAtlasSortFeatureDirectionButton->arrowType();
377 at = ( at == Qt::UpArrow ) ? Qt::DownArrow : Qt::UpArrow;
378 mAtlasSortFeatureDirectionButton->setArrowType( at );
379
380 mBlockUpdates = true;
381 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Sort" ) );
382 mAtlas->setSortAscending( at == Qt::UpArrow );
383 mLayout->undoStack()->endCommand();
384 mBlockUpdates = false;
385 updateAtlasFeatures();
386}
387
388void QgsLayoutAtlasWidget::changeFileFormat()
389{
390 if ( !mLayout )
391 return;
392
393 mLayout->setCustomProperty( QStringLiteral( "atlasRasterFormat" ), mAtlasFileFormat->currentText() );
394}
395
396void QgsLayoutAtlasWidget::updateGuiElements()
397{
398 if ( mBlockUpdates )
399 return;
400
401 blockAllSignals( true );
402 mUseAtlasCheckBox->setCheckState( mAtlas->enabled() ? Qt::Checked : Qt::Unchecked );
403 mConfigurationGroup->setEnabled( mAtlas->enabled() );
404 mOutputGroup->setEnabled( mAtlas->enabled() );
405
406 mAtlasCoverageLayerComboBox->setLayer( mAtlas->coverageLayer() );
407 mPageNameWidget->setLayer( mAtlas->coverageLayer() );
408 mPageNameWidget->setField( mAtlas->pageNameExpression() );
409
410 mAtlasSortExpressionWidget->setLayer( mAtlas->coverageLayer() );
411 mAtlasSortExpressionWidget->setField( mAtlas->sortExpression() );
412
413 mAtlasFilenamePatternEdit->setText( mAtlas->filenameExpression() );
414 mAtlasLimitCoverageLayerRenderCheckBox->setCheckState( mAtlas->limitCoverageLayerRenderToCurrentFeature() ? Qt::Checked : Qt::Unchecked );
415 mAtlasHideCoverageCheckBox->setCheckState( mAtlas->hideCoverage() ? Qt::Checked : Qt::Unchecked );
416
417 const bool singleFile = mLayout->customProperty( QStringLiteral( "singleFile" ) ).toBool();
418 mAtlasSingleFileCheckBox->setCheckState( singleFile ? Qt::Checked : Qt::Unchecked );
419 mAtlasFilenamePatternEdit->setEnabled( !singleFile );
420 mAtlasFilenameExpressionButton->setEnabled( !singleFile );
421
422 mAtlasSortFeatureCheckBox->setCheckState( mAtlas->sortFeatures() ? Qt::Checked : Qt::Unchecked );
423 mAtlasSortFeatureDirectionButton->setEnabled( mAtlas->sortFeatures() );
424 mAtlasSortExpressionWidget->setEnabled( mAtlas->sortFeatures() );
425
426 mAtlasSortFeatureDirectionButton->setArrowType( mAtlas->sortAscending() ? Qt::UpArrow : Qt::DownArrow );
427 mAtlasFeatureFilterEdit->setText( mAtlas->filterExpression() );
428
429 mAtlasFeatureFilterCheckBox->setCheckState( mAtlas->filterFeatures() ? Qt::Checked : Qt::Unchecked );
430 mAtlasFeatureFilterEdit->setEnabled( mAtlas->filterFeatures() );
431 mAtlasFeatureFilterButton->setEnabled( mAtlas->filterFeatures() );
432
433 mAtlasFileFormat->setCurrentIndex( mAtlasFileFormat->findText( mLayout->customProperty( QStringLiteral( "atlasRasterFormat" ), QStringLiteral( "png" ) ).toString() ) );
434
435 blockAllSignals( false );
436}
437
438void QgsLayoutAtlasWidget::blockAllSignals( bool b )
439{
440 mUseAtlasCheckBox->blockSignals( b );
441 mConfigurationGroup->blockSignals( b );
442 mOutputGroup->blockSignals( b );
443 mAtlasCoverageLayerComboBox->blockSignals( b );
444 mPageNameWidget->blockSignals( b );
445 mAtlasSortExpressionWidget->blockSignals( b );
446 mAtlasFilenamePatternEdit->blockSignals( b );
447 mAtlasLimitCoverageLayerRenderCheckBox->blockSignals( b );
448 mAtlasHideCoverageCheckBox->blockSignals( b );
449 mAtlasSingleFileCheckBox->blockSignals( b );
450 mAtlasSortFeatureCheckBox->blockSignals( b );
451 mAtlasSortFeatureDirectionButton->blockSignals( b );
452 mAtlasFeatureFilterEdit->blockSignals( b );
453 mAtlasFeatureFilterCheckBox->blockSignals( b );
454}
A generic dialog for building expression strings.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Handles parsing and evaluation of expressions (formerly called "search strings").
static QString quoteFieldExpression(const QString &expression, const QgsVectorLayer *layer)
Validate if the expression is a field in the layer and ensure it is quoted.
A widget for selection of layer fields or expression creation.
void setLayer(QgsMapLayer *layer)
Sets the layer used to display the fields and expression.
void fieldChanged(const QString &fieldName)
Emitted when the currently selected field changes.
QgsLayoutAtlasWidget(QWidget *parent, QgsPrintLayout *layout)
Constructor.
void setMessageBar(QgsMessageBar *bar)
Sets the message bar to which to emit messages.
QString sortExpression() const
Returns the expression (or field name) to use for sorting features.
bool filterFeatures() const
Returns true if features should be filtered in the coverage layer.
QString filenameExpression() const
Returns the filename expression used for generating output filenames for each atlas page.
bool sortAscending() const
Returns true if features should be sorted in an ascending order.
void setCoverageLayer(QgsVectorLayer *layer)
Sets the coverage layer to use for the atlas features.
bool setFilterExpression(const QString &expression, QString &errorString)
Sets the expression used for filtering features in the coverage layer.
void setSortAscending(bool ascending)
Sets whether features should be sorted in an ascending order.
bool hideCoverage() const
Returns true if the atlas is set to hide the coverage layer.
void setEnabled(bool enabled)
Sets whether the atlas is enabled.
void setPageNameExpression(const QString &expression)
Sets the expression (or field name) used for calculating the page name.
QString filterExpression() const
Returns the expression used for filtering features in the coverage layer.
bool enabled() const
Returns whether the atlas generation is enabled.
bool limitCoverageLayerRenderToCurrentFeature() const
Returns true if the atlas is set to limit rendering on the coverage layer to the current feature.
bool setFilenameExpression(const QString &expression, QString &errorString)
Sets the filename expression used for generating output filenames for each atlas page.
void setLimitCoverageLayerRenderToCurrentFeature(bool limit)
Sets whether the rendering of the coverage layer should be limited to the current feature.
void setSortFeatures(bool enabled)
Sets whether features should be sorted in the atlas.
QString pageNameExpression() const
Returns the expression (or field name) used for calculating the page name.
void setSortExpression(const QString &expression)
Sets the expression (or field name) to use for sorting features.
void setFilterFeatures(bool filtered)
Sets whether features should be filtered in the coverage layer.
QgsVectorLayer * coverageLayer() const
Returns the coverage layer used for the atlas features.
void changed()
Emitted when one of the atlas parameters changes.
int updateFeatures()
Requeries the current atlas coverage layer and applies filtering and sorting.
bool sortFeatures() const
Returns true if features should be sorted in the atlas.
void setHideCoverage(bool hide)
Sets whether the coverage layer should be hidden in map items in the layouts.
void layerChanged(QgsMapLayer *layer)
Emitted whenever the currently selected layer changes.
Base class for all map layer types.
Definition qgsmaplayer.h:78
A bar for displaying non-blocking messages to the user.
void pushInfo(const QString &title, const QString &message)
Pushes a information message with default timeout to the message bar.
void pushWarning(const QString &title, const QString &message)
Pushes a warning message that must be manually dismissed by the user.
Print layout, a QgsLayout subclass for static or atlas-based layouts.
Represents a vector layer which manages a vector based dataset.
QString displayExpression