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