QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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  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 
250 void 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 
263 void 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 
286 void 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 
304 void 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 
325 void 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 
366 void 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 
383 void QgsLayoutAtlasWidget::changeFileFormat()
384 {
385  if ( !mLayout )
386  return;
387 
388  mLayout->setCustomProperty( QStringLiteral( "atlasRasterFormat" ), mAtlasFileFormat->currentText() );
389 }
390 
391 void 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 
432 void 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.
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