QGIS API Documentation 3.41.0-Master (af5edcb665c)
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" ), tr( "Could not set filename expression to '%1'.\nParser error:\n%2" ).arg( mAtlasFilenamePatternEdit->text(), error ) );
142 }
143 mLayout->undoStack()->endCommand();
144 mBlockUpdates = false;
145}
146
147void QgsLayoutAtlasWidget::mAtlasFilenameExpressionButton_clicked()
148{
149 if ( !mLayout || !mAtlas || !mAtlas->coverageLayer() )
150 {
151 return;
152 }
153
154 const QgsExpressionContext context = mLayout->createExpressionContext();
155 QgsExpressionBuilderDialog exprDlg( mAtlas->coverageLayer(), mAtlasFilenamePatternEdit->text(), this, QStringLiteral( "generic" ), context );
156 exprDlg.setWindowTitle( tr( "Expression Based Filename" ) );
157
158 if ( exprDlg.exec() == QDialog::Accepted )
159 {
160 const QString expression = exprDlg.expressionText();
161 if ( !expression.isEmpty() )
162 {
163 //set atlas filename expression
164 mAtlasFilenamePatternEdit->setText( expression );
165 QString error;
166 mBlockUpdates = true;
167 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filename" ) );
168 if ( !mAtlas->setFilenameExpression( expression, error ) )
169 {
170 //expression could not be set
171 mMessageBar->pushWarning( tr( "Atlas" ), tr( "Could not set filename expression to '%1'.\nParser error:\n%2" ).arg( expression, error ) );
172 }
173 mBlockUpdates = false;
174 mLayout->undoStack()->endCommand();
175 }
176 }
177}
178
179void QgsLayoutAtlasWidget::mAtlasHideCoverageCheckBox_stateChanged( int state )
180{
181 if ( !mLayout )
182 return;
183
184 mBlockUpdates = true;
185 mLayout->undoStack()->beginCommand( mAtlas, tr( "Toggle Atlas Layer" ) );
186 mAtlas->setHideCoverage( state == Qt::Checked );
187 mLayout->undoStack()->endCommand();
188 mBlockUpdates = false;
189}
190
191void QgsLayoutAtlasWidget::mAtlasSingleFileCheckBox_stateChanged( int state )
192{
193 if ( !mLayout )
194 return;
195
196 if ( state == Qt::Checked )
197 {
198 mAtlasFilenamePatternEdit->setEnabled( false );
199 mAtlasFilenameExpressionButton->setEnabled( false );
200 }
201 else
202 {
203 mAtlasFilenamePatternEdit->setEnabled( true );
204 mAtlasFilenameExpressionButton->setEnabled( true );
205 }
206
207 mLayout->setCustomProperty( QStringLiteral( "singleFile" ), state == Qt::Checked );
208}
209
210void QgsLayoutAtlasWidget::mAtlasSortFeatureCheckBox_stateChanged( int state )
211{
212 if ( !mLayout )
213 return;
214
215 if ( state == Qt::Checked )
216 {
217 mAtlasSortFeatureDirectionButton->setEnabled( true );
218 mAtlasSortExpressionWidget->setEnabled( true );
219 }
220 else
221 {
222 mAtlasSortFeatureDirectionButton->setEnabled( false );
223 mAtlasSortExpressionWidget->setEnabled( false );
224 }
225 mBlockUpdates = true;
226 mLayout->undoStack()->beginCommand( mAtlas, tr( "Toggle Atlas Sorting" ) );
227 mAtlas->setSortFeatures( state == Qt::Checked );
228 mLayout->undoStack()->endCommand();
229 mBlockUpdates = false;
230 updateAtlasFeatures();
231}
232
233void QgsLayoutAtlasWidget::changesSortFeatureExpression( const QString &expression, bool )
234{
235 if ( !mLayout )
236 return;
237
238 mBlockUpdates = true;
239 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Sort" ) );
240 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mAtlasCoverageLayerComboBox->currentLayer() );
241 mAtlas->setSortExpression( QgsExpression::quoteFieldExpression( expression, vlayer ) );
242 mLayout->undoStack()->endCommand();
243 mBlockUpdates = false;
244 updateAtlasFeatures();
245}
246
247void QgsLayoutAtlasWidget::updateAtlasFeatures()
248{
249 const bool updated = mAtlas->updateFeatures();
250 if ( !updated )
251 {
252 mMessageBar->pushInfo( tr( "Atlas" ), tr( "No matching atlas features found!" ) );
253
254 //Perhaps atlas preview should be disabled now? If so, it may get annoying if user is editing
255 //the filter expression and it keeps disabling itself.
256 }
257}
258
259void QgsLayoutAtlasWidget::mAtlasFeatureFilterCheckBox_stateChanged( int state )
260{
261 if ( !mLayout )
262 return;
263
264 if ( state == Qt::Checked )
265 {
266 mAtlasFeatureFilterEdit->setEnabled( true );
267 mAtlasFeatureFilterButton->setEnabled( true );
268 }
269 else
270 {
271 mAtlasFeatureFilterEdit->setEnabled( false );
272 mAtlasFeatureFilterButton->setEnabled( false );
273 }
274 mBlockUpdates = true;
275 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filter" ) );
276 mAtlas->setFilterFeatures( state == Qt::Checked );
277 mLayout->undoStack()->endCommand();
278 mBlockUpdates = false;
279 updateAtlasFeatures();
280}
281
282void QgsLayoutAtlasWidget::pageNameExpressionChanged( const QString &, bool valid )
283{
284 if ( !mLayout )
285 return;
286
287 const QString expression = mPageNameWidget->asExpression();
288 if ( !valid && !expression.isEmpty() )
289 {
290 return;
291 }
292
293 mBlockUpdates = true;
294 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Name" ) );
295 mAtlas->setPageNameExpression( expression );
296 mLayout->undoStack()->endCommand();
297 mBlockUpdates = false;
298}
299
300void QgsLayoutAtlasWidget::mAtlasFeatureFilterEdit_editingFinished()
301{
302 if ( !mLayout )
303 return;
304
305 QString error;
306 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filter" ) );
307
308 mBlockUpdates = true;
309 if ( !mAtlas->setFilterExpression( mAtlasFeatureFilterEdit->text(), error ) )
310 {
311 //expression could not be set
312 mMessageBar->pushWarning( tr( "Atlas" ), tr( "Could not set filter expression to '%1'.\nParser error:\n%2" ).arg( mAtlasFeatureFilterEdit->text(), error ) );
313 }
314 mBlockUpdates = false;
315 mLayout->undoStack()->endCommand();
316 updateAtlasFeatures();
317}
318
319void QgsLayoutAtlasWidget::mAtlasFeatureFilterButton_clicked()
320{
321 if ( !mLayout )
322 return;
323
324 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mAtlasCoverageLayerComboBox->currentLayer() );
325
326 if ( !vl )
327 {
328 return;
329 }
330
331 const QgsExpressionContext context = mLayout->createExpressionContext();
332 QgsExpressionBuilderDialog exprDlg( vl, mAtlasFeatureFilterEdit->text(), this, QStringLiteral( "generic" ), context );
333 exprDlg.setWindowTitle( tr( "Expression Based Filter" ) );
334
335 if ( exprDlg.exec() == QDialog::Accepted )
336 {
337 const QString expression = exprDlg.expressionText();
338 if ( !expression.isEmpty() )
339 {
340 mAtlasFeatureFilterEdit->setText( expression );
341 QString error;
342 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filter" ) );
343 mBlockUpdates = true;
344 if ( !mAtlas->setFilterExpression( mAtlasFeatureFilterEdit->text(), error ) )
345 {
346 //expression could not be set
347 mMessageBar->pushWarning( tr( "Atlas" ), tr( "Could not set filter expression to '%1'.\nParser error:\n%2" ).arg( mAtlasFeatureFilterEdit->text(), error ) );
348 }
349 mBlockUpdates = false;
350 mLayout->undoStack()->endCommand();
351 updateAtlasFeatures();
352 }
353 }
354}
355
356void QgsLayoutAtlasWidget::mAtlasSortFeatureDirectionButton_clicked()
357{
358 if ( !mLayout )
359 return;
360
361 Qt::ArrowType at = mAtlasSortFeatureDirectionButton->arrowType();
362 at = ( at == Qt::UpArrow ) ? Qt::DownArrow : Qt::UpArrow;
363 mAtlasSortFeatureDirectionButton->setArrowType( at );
364
365 mBlockUpdates = true;
366 mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Sort" ) );
367 mAtlas->setSortAscending( at == Qt::UpArrow );
368 mLayout->undoStack()->endCommand();
369 mBlockUpdates = false;
370 updateAtlasFeatures();
371}
372
373void QgsLayoutAtlasWidget::changeFileFormat()
374{
375 if ( !mLayout )
376 return;
377
378 mLayout->setCustomProperty( QStringLiteral( "atlasRasterFormat" ), mAtlasFileFormat->currentText() );
379}
380
381void QgsLayoutAtlasWidget::updateGuiElements()
382{
383 if ( mBlockUpdates )
384 return;
385
386 blockAllSignals( true );
387 mUseAtlasCheckBox->setCheckState( mAtlas->enabled() ? Qt::Checked : Qt::Unchecked );
388 mConfigurationGroup->setEnabled( mAtlas->enabled() );
389 mOutputGroup->setEnabled( mAtlas->enabled() );
390
391 mAtlasCoverageLayerComboBox->setLayer( mAtlas->coverageLayer() );
392 mPageNameWidget->setLayer( mAtlas->coverageLayer() );
393 mPageNameWidget->setField( mAtlas->pageNameExpression() );
394
395 mAtlasSortExpressionWidget->setLayer( mAtlas->coverageLayer() );
396 mAtlasSortExpressionWidget->setField( mAtlas->sortExpression() );
397
398 mAtlasFilenamePatternEdit->setText( mAtlas->filenameExpression() );
399 mAtlasHideCoverageCheckBox->setCheckState( mAtlas->hideCoverage() ? Qt::Checked : Qt::Unchecked );
400
401 const bool singleFile = mLayout->customProperty( QStringLiteral( "singleFile" ) ).toBool();
402 mAtlasSingleFileCheckBox->setCheckState( singleFile ? Qt::Checked : Qt::Unchecked );
403 mAtlasFilenamePatternEdit->setEnabled( !singleFile );
404 mAtlasFilenameExpressionButton->setEnabled( !singleFile );
405
406 mAtlasSortFeatureCheckBox->setCheckState( mAtlas->sortFeatures() ? Qt::Checked : Qt::Unchecked );
407 mAtlasSortFeatureDirectionButton->setEnabled( mAtlas->sortFeatures() );
408 mAtlasSortExpressionWidget->setEnabled( mAtlas->sortFeatures() );
409
410 mAtlasSortFeatureDirectionButton->setArrowType( mAtlas->sortAscending() ? Qt::UpArrow : Qt::DownArrow );
411 mAtlasFeatureFilterEdit->setText( mAtlas->filterExpression() );
412
413 mAtlasFeatureFilterCheckBox->setCheckState( mAtlas->filterFeatures() ? Qt::Checked : Qt::Unchecked );
414 mAtlasFeatureFilterEdit->setEnabled( mAtlas->filterFeatures() );
415 mAtlasFeatureFilterButton->setEnabled( mAtlas->filterFeatures() );
416
417 mAtlasFileFormat->setCurrentIndex( mAtlasFileFormat->findText( mLayout->customProperty( QStringLiteral( "atlasRasterFormat" ), QStringLiteral( "png" ) ).toString() ) );
418
419 blockAllSignals( false );
420}
421
422void QgsLayoutAtlasWidget::blockAllSignals( bool b )
423{
424 mUseAtlasCheckBox->blockSignals( b );
425 mConfigurationGroup->blockSignals( b );
426 mOutputGroup->blockSignals( b );
427 mAtlasCoverageLayerComboBox->blockSignals( b );
428 mPageNameWidget->blockSignals( b );
429 mAtlasSortExpressionWidget->blockSignals( b );
430 mAtlasFilenamePatternEdit->blockSignals( b );
431 mAtlasHideCoverageCheckBox->blockSignals( b );
432 mAtlasSingleFileCheckBox->blockSignals( b );
433 mAtlasSortFeatureCheckBox->blockSignals( b );
434 mAtlasSortFeatureDirectionButton->blockSignals( b );
435 mAtlasFeatureFilterEdit->blockSignals( b );
436 mAtlasFeatureFilterCheckBox->blockSignals( b );
437}
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