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