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