QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 
20 #include "qgslayoutatlaswidget.h"
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 
77 void 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 
93 void 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  {
122  mAtlas->setPageNameExpression( vl->displayExpression() );
123  }
124 
125  mLayout->undoStack()->endCommand();
126 }
127 
128 void 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 
148 void 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 
182 void 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 
194 void 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 
213 void 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 
236 void 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  mAtlas->setSortExpression( expression );
244  mLayout->undoStack()->endCommand();
245  mBlockUpdates = false;
246  updateAtlasFeatures();
247 }
248 
249 void QgsLayoutAtlasWidget::updateAtlasFeatures()
250 {
251  const bool updated = mAtlas->updateFeatures();
252  if ( !updated )
253  {
254  mMessageBar->pushInfo( tr( "Atlas" ),
255  tr( "No matching atlas features found!" ) );
256 
257  //Perhaps atlas preview should be disabled now? If so, it may get annoying if user is editing
258  //the filter expression and it keeps disabling itself.
259  }
260 }
261 
262 void QgsLayoutAtlasWidget::mAtlasFeatureFilterCheckBox_stateChanged( int state )
263 {
264  if ( !mLayout )
265  return;
266 
267  if ( state == Qt::Checked )
268  {
269  mAtlasFeatureFilterEdit->setEnabled( true );
270  mAtlasFeatureFilterButton->setEnabled( true );
271  }
272  else
273  {
274  mAtlasFeatureFilterEdit->setEnabled( false );
275  mAtlasFeatureFilterButton->setEnabled( false );
276  }
277  mBlockUpdates = true;
278  mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filter" ) );
279  mAtlas->setFilterFeatures( state == Qt::Checked );
280  mLayout->undoStack()->endCommand();
281  mBlockUpdates = false;
282  updateAtlasFeatures();
283 }
284 
285 void QgsLayoutAtlasWidget::pageNameExpressionChanged( const QString &, bool valid )
286 {
287  if ( !mLayout )
288  return;
289 
290  const QString expression = mPageNameWidget->asExpression();
291  if ( !valid && !expression.isEmpty() )
292  {
293  return;
294  }
295 
296  mBlockUpdates = true;
297  mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Name" ) );
298  mAtlas->setPageNameExpression( expression );
299  mLayout->undoStack()->endCommand();
300  mBlockUpdates = false;
301 }
302 
303 void QgsLayoutAtlasWidget::mAtlasFeatureFilterEdit_editingFinished()
304 {
305  if ( !mLayout )
306  return;
307 
308  QString error;
309  mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filter" ) );
310 
311  mBlockUpdates = true;
312  if ( !mAtlas->setFilterExpression( mAtlasFeatureFilterEdit->text(), error ) )
313  {
314  //expression could not be set
315  mMessageBar->pushWarning( tr( "Atlas" ), tr( "Could not set filter expression to '%1'.\nParser error:\n%2" )
316  .arg( mAtlasFeatureFilterEdit->text(),
317  error ) );
318  }
319  mBlockUpdates = false;
320  mLayout->undoStack()->endCommand();
321  updateAtlasFeatures();
322 }
323 
324 void QgsLayoutAtlasWidget::mAtlasFeatureFilterButton_clicked()
325 {
326  if ( !mLayout )
327  return;
328 
329  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mAtlasCoverageLayerComboBox->currentLayer() );
330 
331  if ( !vl )
332  {
333  return;
334  }
335 
336  const QgsExpressionContext context = mLayout->createExpressionContext();
337  QgsExpressionBuilderDialog exprDlg( vl, mAtlasFeatureFilterEdit->text(), this, QStringLiteral( "generic" ), context );
338  exprDlg.setWindowTitle( tr( "Expression Based Filter" ) );
339 
340  if ( exprDlg.exec() == QDialog::Accepted )
341  {
342  const QString expression = exprDlg.expressionText();
343  if ( !expression.isEmpty() )
344  {
345  mAtlasFeatureFilterEdit->setText( expression );
346  QString error;
347  mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Filter" ) );
348  mBlockUpdates = true;
349  if ( !mAtlas->setFilterExpression( mAtlasFeatureFilterEdit->text(), error ) )
350  {
351  //expression could not be set
352  mMessageBar->pushWarning( tr( "Atlas" ),
353  tr( "Could not set filter expression to '%1'.\nParser error:\n%2" )
354  .arg( mAtlasFeatureFilterEdit->text(),
355  error )
356  );
357  }
358  mBlockUpdates = false;
359  mLayout->undoStack()->endCommand();
360  updateAtlasFeatures();
361  }
362  }
363 }
364 
365 void QgsLayoutAtlasWidget::mAtlasSortFeatureDirectionButton_clicked()
366 {
367  if ( !mLayout )
368  return;
369 
370  Qt::ArrowType at = mAtlasSortFeatureDirectionButton->arrowType();
371  at = ( at == Qt::UpArrow ) ? Qt::DownArrow : Qt::UpArrow;
372  mAtlasSortFeatureDirectionButton->setArrowType( at );
373 
374  mBlockUpdates = true;
375  mLayout->undoStack()->beginCommand( mAtlas, tr( "Change Atlas Sort" ) );
376  mAtlas->setSortAscending( at == Qt::UpArrow );
377  mLayout->undoStack()->endCommand();
378  mBlockUpdates = false;
379  updateAtlasFeatures();
380 }
381 
382 void QgsLayoutAtlasWidget::changeFileFormat()
383 {
384  if ( !mLayout )
385  return;
386 
387  mLayout->setCustomProperty( QStringLiteral( "atlasRasterFormat" ), mAtlasFileFormat->currentText() );
388 }
389 
390 void QgsLayoutAtlasWidget::updateGuiElements()
391 {
392  if ( mBlockUpdates )
393  return;
394 
395  blockAllSignals( true );
396  mUseAtlasCheckBox->setCheckState( mAtlas->enabled() ? Qt::Checked : Qt::Unchecked );
397  mConfigurationGroup->setEnabled( mAtlas->enabled() );
398  mOutputGroup->setEnabled( mAtlas->enabled() );
399 
400  mAtlasCoverageLayerComboBox->setLayer( mAtlas->coverageLayer() );
401  mPageNameWidget->setLayer( mAtlas->coverageLayer() );
402  mPageNameWidget->setField( mAtlas->pageNameExpression() );
403 
404  mAtlasSortExpressionWidget->setLayer( mAtlas->coverageLayer() );
405  mAtlasSortExpressionWidget->setField( mAtlas->sortExpression() );
406 
407  mAtlasFilenamePatternEdit->setText( mAtlas->filenameExpression() );
408  mAtlasHideCoverageCheckBox->setCheckState( mAtlas->hideCoverage() ? Qt::Checked : Qt::Unchecked );
409 
410  const bool singleFile = mLayout->customProperty( QStringLiteral( "singleFile" ) ).toBool();
411  mAtlasSingleFileCheckBox->setCheckState( singleFile ? Qt::Checked : Qt::Unchecked );
412  mAtlasFilenamePatternEdit->setEnabled( !singleFile );
413  mAtlasFilenameExpressionButton->setEnabled( !singleFile );
414 
415  mAtlasSortFeatureCheckBox->setCheckState( mAtlas->sortFeatures() ? Qt::Checked : Qt::Unchecked );
416  mAtlasSortFeatureDirectionButton->setEnabled( mAtlas->sortFeatures() );
417  mAtlasSortExpressionWidget->setEnabled( mAtlas->sortFeatures() );
418 
419  mAtlasSortFeatureDirectionButton->setArrowType( mAtlas->sortAscending() ? Qt::UpArrow : Qt::DownArrow );
420  mAtlasFeatureFilterEdit->setText( mAtlas->filterExpression() );
421 
422  mAtlasFeatureFilterCheckBox->setCheckState( mAtlas->filterFeatures() ? Qt::Checked : Qt::Unchecked );
423  mAtlasFeatureFilterEdit->setEnabled( mAtlas->filterFeatures() );
424  mAtlasFeatureFilterButton->setEnabled( mAtlas->filterFeatures() );
425 
426  mAtlasFileFormat->setCurrentIndex( mAtlasFileFormat->findText( mLayout->customProperty( QStringLiteral( "atlasRasterFormat" ), QStringLiteral( "png" ) ).toString() ) );
427 
428  blockAllSignals( false );
429 }
430 
431 void QgsLayoutAtlasWidget::blockAllSignals( bool b )
432 {
433  mUseAtlasCheckBox->blockSignals( b );
434  mConfigurationGroup->blockSignals( b );
435  mOutputGroup->blockSignals( b );
436  mAtlasCoverageLayerComboBox->blockSignals( b );
437  mPageNameWidget->blockSignals( b );
438  mAtlasSortExpressionWidget->blockSignals( b );
439  mAtlasFilenamePatternEdit->blockSignals( b );
440  mAtlasHideCoverageCheckBox->blockSignals( b );
441  mAtlasSingleFileCheckBox->blockSignals( b );
442  mAtlasSortFeatureCheckBox->blockSignals( b );
443  mAtlasSortFeatureDirectionButton->blockSignals( b );
444  mAtlasFeatureFilterEdit->blockSignals( b );
445  mAtlasFeatureFilterCheckBox->blockSignals( b );
446 }
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").
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.
QgsVectorLayer * coverageLayer() const
Returns the coverage layer used for the atlas 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.
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