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