QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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 "qgsprintlayout.h"
22#include "qgslayoutatlas.h"
24#include "qgslayoutundostack.h"
26#include "qgsmessagebar.h"
28
30 : QWidget( parent )
31 , mLayout( layout )
32 , mAtlas( layout->atlas() )
33{
34 setupUi( this );
35 connect( mUseAtlasCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutAtlasWidget::mUseAtlasCheckBox_stateChanged );
36 connect( mAtlasFilenamePatternEdit, &QLineEdit::editingFinished, this, &QgsLayoutAtlasWidget::mAtlasFilenamePatternEdit_editingFinished );
37 connect( mAtlasFilenameExpressionButton, &QToolButton::clicked, this, &QgsLayoutAtlasWidget::mAtlasFilenameExpressionButton_clicked );
38 connect( mAtlasHideCoverageCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutAtlasWidget::mAtlasHideCoverageCheckBox_stateChanged );
39 connect( mAtlasSingleFileCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutAtlasWidget::mAtlasSingleFileCheckBox_stateChanged );
40 connect( mAtlasSortFeatureCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutAtlasWidget::mAtlasSortFeatureCheckBox_stateChanged );
41 connect( mAtlasSortFeatureDirectionButton, &QToolButton::clicked, this, &QgsLayoutAtlasWidget::mAtlasSortFeatureDirectionButton_clicked );
42 connect( mAtlasFeatureFilterEdit, &QLineEdit::editingFinished, this, &QgsLayoutAtlasWidget::mAtlasFeatureFilterEdit_editingFinished );
43 connect( mAtlasFeatureFilterButton, &QToolButton::clicked, this, &QgsLayoutAtlasWidget::mAtlasFeatureFilterButton_clicked );
44 connect( mAtlasFeatureFilterCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutAtlasWidget::mAtlasFeatureFilterCheckBox_stateChanged );
45
46 mAtlasCoverageLayerComboBox->setFilters( Qgis::LayerFilter::VectorLayer );
47
48 connect( mAtlasCoverageLayerComboBox, &QgsMapLayerComboBox::layerChanged, mAtlasSortExpressionWidget, &QgsFieldExpressionWidget::setLayer );
49 connect( mAtlasCoverageLayerComboBox, &QgsMapLayerComboBox::layerChanged, mPageNameWidget, &QgsFieldExpressionWidget::setLayer );
50 connect( mAtlasCoverageLayerComboBox, &QgsMapLayerComboBox::layerChanged, this, &QgsLayoutAtlasWidget::changeCoverageLayer );
51 connect( mAtlasSortExpressionWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString &, bool ) > ( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsLayoutAtlasWidget::changesSortFeatureExpression );
52 connect( mPageNameWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString &, bool ) > ( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsLayoutAtlasWidget::pageNameExpressionChanged );
53
54 // Sort direction
55 mAtlasSortFeatureDirectionButton->setEnabled( false );
56 mAtlasSortExpressionWidget->setEnabled( false );
57
58 // connect to updates
59 connect( mAtlas, &QgsLayoutAtlas::changed, this, &QgsLayoutAtlasWidget::updateGuiElements );
60
61 mPageNameWidget->registerExpressionContextGenerator( mLayout );
62
63 const QList<QByteArray> formats = QImageWriter::supportedImageFormats();
64 for ( int i = 0; i < formats.size(); ++i )
65 {
66 mAtlasFileFormat->addItem( QString( formats.at( i ) ) );
67 }
68 connect( mAtlasFileFormat, qOverload<int>( &QComboBox::currentIndexChanged ), this, [ = ]( int ) { changeFileFormat(); } );
69
70 updateGuiElements();
71}
72
74{
75 mMessageBar = bar;
76}
77
78void QgsLayoutAtlasWidget::mUseAtlasCheckBox_stateChanged( int state )
79{
80 if ( state == Qt::Checked )
81 {
82 mAtlas->setEnabled( true );
83 mConfigurationGroup->setEnabled( true );
84 mOutputGroup->setEnabled( true );
85 }
86 else
87 {
88 mAtlas->setEnabled( false );
89 mConfigurationGroup->setEnabled( false );
90 mOutputGroup->setEnabled( false );
91 }
92}
93
94void QgsLayoutAtlasWidget::changeCoverageLayer( QgsMapLayer *layer )
95{
96 if ( !mLayout )
97 return;
98
99 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
100
101 const QString prevPageNameExpression = mAtlas->pageNameExpression();
102 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Layer" ) );
103 mLayout->reportContext().setLayer( vl );
104 if ( !vl )
105 {
106 mAtlas->setCoverageLayer( nullptr );
107 }
108 else
109 {
110 mAtlas->setCoverageLayer( vl );
111 updateAtlasFeatures();
112 }
113
114 // if page name expression is still valid, retain it. Otherwise switch to a nice default.
115 QgsExpression exp( prevPageNameExpression );
117 if ( exp.prepare( &context ) && !exp.hasParserError() )
118 {
119 mAtlas->setPageNameExpression( prevPageNameExpression );
120 }
121 else if ( vl )
122 {
124 }
125
126 mLayout->undoStack()->endCommand();
127}
128
129void QgsLayoutAtlasWidget::mAtlasFilenamePatternEdit_editingFinished()
130{
131 if ( !mLayout )
132 return;
133
134 QString error;
135 mBlockUpdates = true;
136 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filename" ) );
137 if ( !mAtlas->setFilenameExpression( mAtlasFilenamePatternEdit->text(), error ) )
138 {
139 //expression could not be set
140 mMessageBar->pushWarning( tr( "Atlas" ),
141 tr( "Could not set filename expression to '%1'.\nParser error:\n%2" )
142 .arg( mAtlasFilenamePatternEdit->text(),
143 error ) );
144 }
145 mLayout->undoStack()->endCommand();
146 mBlockUpdates = false;
147}
148
149void QgsLayoutAtlasWidget::mAtlasFilenameExpressionButton_clicked()
150{
151 if ( !mLayout || !mAtlas || !mAtlas->coverageLayer() )
152 {
153 return;
154 }
155
156 const QgsExpressionContext context = mLayout->createExpressionContext();
157 QgsExpressionBuilderDialog exprDlg( mAtlas->coverageLayer(), mAtlasFilenamePatternEdit->text(), this, QStringLiteral( "generic" ), context );
158 exprDlg.setWindowTitle( tr( "Expression Based Filename" ) );
159
160 if ( exprDlg.exec() == QDialog::Accepted )
161 {
162 const QString expression = exprDlg.expressionText();
163 if ( !expression.isEmpty() )
164 {
165 //set atlas filename expression
166 mAtlasFilenamePatternEdit->setText( expression );
167 QString error;
168 mBlockUpdates = true;
169 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filename" ) );
170 if ( !mAtlas->setFilenameExpression( expression, error ) )
171 {
172 //expression could not be set
173 mMessageBar->pushWarning( tr( "Atlas" ), tr( "Could not set filename expression to '%1'.\nParser error:\n%2" )
174 .arg( expression,
175 error ) );
176 }
177 mBlockUpdates = false;
178 mLayout->undoStack()->endCommand();
179 }
180 }
181}
182
183void QgsLayoutAtlasWidget::mAtlasHideCoverageCheckBox_stateChanged( int state )
184{
185 if ( !mLayout )
186 return;
187
188 mBlockUpdates = true;
189 mLayout->undoStack()->beginCommand( mAtlas, tr( "Toggle Atlas Layer" ) );
190 mAtlas->setHideCoverage( state == Qt::Checked );
191 mLayout->undoStack()->endCommand();
192 mBlockUpdates = false;
193}
194
195void QgsLayoutAtlasWidget::mAtlasSingleFileCheckBox_stateChanged( int state )
196{
197 if ( !mLayout )
198 return;
199
200 if ( state == Qt::Checked )
201 {
202 mAtlasFilenamePatternEdit->setEnabled( false );
203 mAtlasFilenameExpressionButton->setEnabled( false );
204 }
205 else
206 {
207 mAtlasFilenamePatternEdit->setEnabled( true );
208 mAtlasFilenameExpressionButton->setEnabled( true );
209 }
210
211 mLayout->setCustomProperty( QStringLiteral( "singleFile" ), state == Qt::Checked );
212}
213
214void QgsLayoutAtlasWidget::mAtlasSortFeatureCheckBox_stateChanged( int state )
215{
216 if ( !mLayout )
217 return;
218
219 if ( state == Qt::Checked )
220 {
221 mAtlasSortFeatureDirectionButton->setEnabled( true );
222 mAtlasSortExpressionWidget->setEnabled( true );
223 }
224 else
225 {
226 mAtlasSortFeatureDirectionButton->setEnabled( false );
227 mAtlasSortExpressionWidget->setEnabled( false );
228 }
229 mBlockUpdates = true;
230 mLayout->undoStack()->beginCommand( mAtlas, tr( "Toggle Atlas Sorting" ) );
231 mAtlas->setSortFeatures( state == Qt::Checked );
232 mLayout->undoStack()->endCommand();
233 mBlockUpdates = false;
234 updateAtlasFeatures();
235}
236
237void QgsLayoutAtlasWidget::changesSortFeatureExpression( const QString &expression, bool )
238{
239 if ( !mLayout )
240 return;
241
242 mBlockUpdates = true;
243 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Sort" ) );
244 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mAtlasCoverageLayerComboBox->currentLayer() );
245 mAtlas->setSortExpression( QgsExpression::quoteFieldExpression( expression, vlayer ) );
246 mLayout->undoStack()->endCommand();
247 mBlockUpdates = false;
248 updateAtlasFeatures();
249}
250
251void QgsLayoutAtlasWidget::updateAtlasFeatures()
252{
253 const bool updated = mAtlas->updateFeatures();
254 if ( !updated )
255 {
256 mMessageBar->pushInfo( tr( "Atlas" ),
257 tr( "No matching atlas features found!" ) );
258
259 //Perhaps atlas preview should be disabled now? If so, it may get annoying if user is editing
260 //the filter expression and it keeps disabling itself.
261 }
262}
263
264void QgsLayoutAtlasWidget::mAtlasFeatureFilterCheckBox_stateChanged( int state )
265{
266 if ( !mLayout )
267 return;
268
269 if ( state == Qt::Checked )
270 {
271 mAtlasFeatureFilterEdit->setEnabled( true );
272 mAtlasFeatureFilterButton->setEnabled( true );
273 }
274 else
275 {
276 mAtlasFeatureFilterEdit->setEnabled( false );
277 mAtlasFeatureFilterButton->setEnabled( false );
278 }
279 mBlockUpdates = true;
280 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filter" ) );
281 mAtlas->setFilterFeatures( state == Qt::Checked );
282 mLayout->undoStack()->endCommand();
283 mBlockUpdates = false;
284 updateAtlasFeatures();
285}
286
287void QgsLayoutAtlasWidget::pageNameExpressionChanged( const QString &, bool valid )
288{
289 if ( !mLayout )
290 return;
291
292 const QString expression = mPageNameWidget->asExpression();
293 if ( !valid && !expression.isEmpty() )
294 {
295 return;
296 }
297
298 mBlockUpdates = true;
299 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Name" ) );
300 mAtlas->setPageNameExpression( expression );
301 mLayout->undoStack()->endCommand();
302 mBlockUpdates = false;
303}
304
305void QgsLayoutAtlasWidget::mAtlasFeatureFilterEdit_editingFinished()
306{
307 if ( !mLayout )
308 return;
309
310 QString error;
311 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filter" ) );
312
313 mBlockUpdates = true;
314 if ( !mAtlas->setFilterExpression( mAtlasFeatureFilterEdit->text(), error ) )
315 {
316 //expression could not be set
317 mMessageBar->pushWarning( tr( "Atlas" ), tr( "Could not set filter expression to '%1'.\nParser error:\n%2" )
318 .arg( mAtlasFeatureFilterEdit->text(),
319 error ) );
320 }
321 mBlockUpdates = false;
322 mLayout->undoStack()->endCommand();
323 updateAtlasFeatures();
324}
325
326void QgsLayoutAtlasWidget::mAtlasFeatureFilterButton_clicked()
327{
328 if ( !mLayout )
329 return;
330
331 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mAtlasCoverageLayerComboBox->currentLayer() );
332
333 if ( !vl )
334 {
335 return;
336 }
337
338 const QgsExpressionContext context = mLayout->createExpressionContext();
339 QgsExpressionBuilderDialog exprDlg( vl, mAtlasFeatureFilterEdit->text(), this, QStringLiteral( "generic" ), context );
340 exprDlg.setWindowTitle( tr( "Expression Based Filter" ) );
341
342 if ( exprDlg.exec() == QDialog::Accepted )
343 {
344 const QString expression = exprDlg.expressionText();
345 if ( !expression.isEmpty() )
346 {
347 mAtlasFeatureFilterEdit->setText( expression );
348 QString error;
349 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filter" ) );
350 mBlockUpdates = true;
351 if ( !mAtlas->setFilterExpression( mAtlasFeatureFilterEdit->text(), error ) )
352 {
353 //expression could not be set
354 mMessageBar->pushWarning( tr( "Atlas" ),
355 tr( "Could not set filter expression to '%1'.\nParser error:\n%2" )
356 .arg( mAtlasFeatureFilterEdit->text(),
357 error )
358 );
359 }
360 mBlockUpdates = false;
361 mLayout->undoStack()->endCommand();
362 updateAtlasFeatures();
363 }
364 }
365}
366
367void QgsLayoutAtlasWidget::mAtlasSortFeatureDirectionButton_clicked()
368{
369 if ( !mLayout )
370 return;
371
372 Qt::ArrowType at = mAtlasSortFeatureDirectionButton->arrowType();
373 at = ( at == Qt::UpArrow ) ? Qt::DownArrow : Qt::UpArrow;
374 mAtlasSortFeatureDirectionButton->setArrowType( at );
375
376 mBlockUpdates = true;
377 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Sort" ) );
378 mAtlas->setSortAscending( at == Qt::UpArrow );
379 mLayout->undoStack()->endCommand();
380 mBlockUpdates = false;
381 updateAtlasFeatures();
382}
383
384void QgsLayoutAtlasWidget::changeFileFormat()
385{
386 if ( !mLayout )
387 return;
388
389 mLayout->setCustomProperty( QStringLiteral( "atlasRasterFormat" ), mAtlasFileFormat->currentText() );
390}
391
392void QgsLayoutAtlasWidget::updateGuiElements()
393{
394 if ( mBlockUpdates )
395 return;
396
397 blockAllSignals( true );
398 mUseAtlasCheckBox->setCheckState( mAtlas->enabled() ? Qt::Checked : Qt::Unchecked );
399 mConfigurationGroup->setEnabled( mAtlas->enabled() );
400 mOutputGroup->setEnabled( mAtlas->enabled() );
401
402 mAtlasCoverageLayerComboBox->setLayer( mAtlas->coverageLayer() );
403 mPageNameWidget->setLayer( mAtlas->coverageLayer() );
404 mPageNameWidget->setField( mAtlas->pageNameExpression() );
405
406 mAtlasSortExpressionWidget->setLayer( mAtlas->coverageLayer() );
407 mAtlasSortExpressionWidget->setField( mAtlas->sortExpression() );
408
409 mAtlasFilenamePatternEdit->setText( mAtlas->filenameExpression() );
410 mAtlasHideCoverageCheckBox->setCheckState( mAtlas->hideCoverage() ? Qt::Checked : Qt::Unchecked );
411
412 const bool singleFile = mLayout->customProperty( QStringLiteral( "singleFile" ) ).toBool();
413 mAtlasSingleFileCheckBox->setCheckState( singleFile ? Qt::Checked : Qt::Unchecked );
414 mAtlasFilenamePatternEdit->setEnabled( !singleFile );
415 mAtlasFilenameExpressionButton->setEnabled( !singleFile );
416
417 mAtlasSortFeatureCheckBox->setCheckState( mAtlas->sortFeatures() ? Qt::Checked : Qt::Unchecked );
418 mAtlasSortFeatureDirectionButton->setEnabled( mAtlas->sortFeatures() );
419 mAtlasSortExpressionWidget->setEnabled( mAtlas->sortFeatures() );
420
421 mAtlasSortFeatureDirectionButton->setArrowType( mAtlas->sortAscending() ? Qt::UpArrow : Qt::DownArrow );
422 mAtlasFeatureFilterEdit->setText( mAtlas->filterExpression() );
423
424 mAtlasFeatureFilterCheckBox->setCheckState( mAtlas->filterFeatures() ? Qt::Checked : Qt::Unchecked );
425 mAtlasFeatureFilterEdit->setEnabled( mAtlas->filterFeatures() );
426 mAtlasFeatureFilterButton->setEnabled( mAtlas->filterFeatures() );
427
428 mAtlasFileFormat->setCurrentIndex( mAtlasFileFormat->findText( mLayout->customProperty( QStringLiteral( "atlasRasterFormat" ), QStringLiteral( "png" ) ).toString() ) );
429
430 blockAllSignals( false );
431}
432
433void QgsLayoutAtlasWidget::blockAllSignals( bool b )
434{
435 mUseAtlasCheckBox->blockSignals( b );
436 mConfigurationGroup->blockSignals( b );
437 mOutputGroup->blockSignals( b );
438 mAtlasCoverageLayerComboBox->blockSignals( b );
439 mPageNameWidget->blockSignals( b );
440 mAtlasSortExpressionWidget->blockSignals( b );
441 mAtlasFilenamePatternEdit->blockSignals( b );
442 mAtlasHideCoverageCheckBox->blockSignals( b );
443 mAtlasSingleFileCheckBox->blockSignals( b );
444 mAtlasSortFeatureCheckBox->blockSignals( b );
445 mAtlasSortFeatureDirectionButton->blockSignals( b );
446 mAtlasFeatureFilterEdit->blockSignals( b );
447 mAtlasFeatureFilterCheckBox->blockSignals( b );
448}
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...
Class for 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.
The QgsFieldExpressionWidget class creates a widget to choose fields and edit expressions It contains...
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 setFilenameExpression(const QString &expression, QString &errorString)
Sets the filename expression used for generating output filenames for each atlas page.
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:75
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:61
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 data sets.
QString displayExpression