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