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