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"
23 #include "qgsexpressionbuilderdialog.h"
24 #include "qgslayoutundostack.h"
25 #include "qgsmessagebar.h"
26
QgsLayoutAtlasWidget(QWidget * parent,QgsPrintLayout * layout)27 QgsLayoutAtlasWidget::QgsLayoutAtlasWidget( QWidget *parent, QgsPrintLayout *layout )
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, qgis::overload<int>::of( &QComboBox::currentIndexChanged ), this, [ = ]( int ) { changeFileFormat(); } );
67
68 updateGuiElements();
69 }
70
setMessageBar(QgsMessageBar * bar)71 void QgsLayoutAtlasWidget::setMessageBar( QgsMessageBar *bar )
72 {
73 mMessageBar = bar;
74 }
75
mUseAtlasCheckBox_stateChanged(int state)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
changeCoverageLayer(QgsMapLayer * layer)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
mAtlasFilenamePatternEdit_editingFinished()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
mAtlasFilenameExpressionButton_clicked()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
mAtlasHideCoverageCheckBox_stateChanged(int state)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
mAtlasSingleFileCheckBox_stateChanged(int state)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
mAtlasSortFeatureCheckBox_stateChanged(int state)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
changesSortFeatureExpression(const QString & expression,bool)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
updateAtlasFeatures()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
mAtlasFeatureFilterCheckBox_stateChanged(int state)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
pageNameExpressionChanged(const QString &,bool valid)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
mAtlasFeatureFilterEdit_editingFinished()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
mAtlasFeatureFilterButton_clicked()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
mAtlasSortFeatureDirectionButton_clicked()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
changeFileFormat()367 void QgsLayoutAtlasWidget::changeFileFormat()
368 {
369 if ( !mLayout )
370 return;
371
372 mLayout->setCustomProperty( QStringLiteral( "atlasRasterFormat" ), mAtlasFileFormat->currentText() );
373 }
374
updateGuiElements()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" ) ).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
blockAllSignals(bool b)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 }
432