1 /***************************************************************************
2                           qgsmeshcalculatordialog.cpp
3                           ---------------------------
4     begin                : January 2019
5     copyright            : (C) 2018 by Peter Petrik
6     email                : zilolv at gmail 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 "qgsgdalutils.h"
18 #include "qgsmeshcalculatordialog.h"
19 #include "qgsproject.h"
20 #include "qgsmeshcalcnode.h"
21 #include "qgsmeshdataprovider.h"
22 #include "qgsproviderregistry.h"
23 #include "qgsmeshlayer.h"
24 #include "qgssettings.h"
25 #include "qgsgui.h"
26 #include "qgsvectorlayer.h"
27 #include "qgsmaplayerproxymodel.h"
28 #include "qgswkbtypes.h"
29 #include "qgsfeatureiterator.h"
30 #include "qgsmeshdatasetgrouptreeview.h"
31 #include "qgshelp.h"
32 
33 #include "cpl_string.h"
34 #include "gdal.h"
35 #include "qgis.h"
36 
37 #include <QMessageBox>
38 #include <QFileDialog>
39 #include <QFontDatabase>
40 #include <QMap>
41 
QgsMeshCalculatorDialog(QgsMeshLayer * meshLayer,QWidget * parent,Qt::WindowFlags f)42 QgsMeshCalculatorDialog::QgsMeshCalculatorDialog( QgsMeshLayer *meshLayer, QWidget *parent, Qt::WindowFlags f )
43   : QDialog( parent, f ),
44     mLayer( meshLayer )
45 {
46   setupUi( this );
47   QgsGui::enableAutoGeometryRestore( this );
48 
49   cboLayerMask->setFilters( QgsMapLayerProxyModel::PolygonLayer );
50   QgsMeshDatasetGroupListModel *model = new QgsMeshDatasetGroupListModel( this );
51   model->syncToLayer( meshLayer );
52   model->setDisplayProviderName( true );
53   mDatasetsListWidget->setModel( model );
54   mVariableNames = model->variableNames();
55 
56   getMeshDrivers();
57   populateDriversComboBox( );
58   connect( mOutputFormatComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsMeshCalculatorDialog::updateInfoMessage );
59   connect( mOutputFormatComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsMeshCalculatorDialog::onOutputFormatChange );
60   connect( mOutputGroupNameLineEdit, &QLineEdit::textChanged, this, &QgsMeshCalculatorDialog::updateInfoMessage );
61 
62   connect( mDatasetsListWidget, &QListView::doubleClicked, this, &QgsMeshCalculatorDialog::datasetGroupEntry );
63   connect( mCurrentLayerExtentButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mCurrentLayerExtentButton_clicked );
64   connect( mAllTimesButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mAllTimesButton_clicked );
65   connect( mExpressionTextEdit, &QTextEdit::textChanged, this, &QgsMeshCalculatorDialog::updateInfoMessage );
66 
67   connect( useMaskCb, &QRadioButton::toggled, this, &QgsMeshCalculatorDialog::toggleExtendMask );
68   maskBox->setVisible( false );
69   useMaskCb->setEnabled( cboLayerMask->count() );
70 
71   mXMaxSpinBox->setShowClearButton( false );
72   mXMinSpinBox->setShowClearButton( false );
73   mYMaxSpinBox->setShowClearButton( false );
74   mYMinSpinBox->setShowClearButton( false );
75 
76   connect( mPlusPushButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mPlusPushButton_clicked );
77   connect( mMinusPushButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mMinusPushButton_clicked );
78   connect( mLessButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mLessButton_clicked );
79   connect( mLesserEqualButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mLesserEqualButton_clicked );
80   connect( mMultiplyPushButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mMultiplyPushButton_clicked );
81   connect( mDividePushButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mDividePushButton_clicked );
82   connect( mGreaterButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mGreaterButton_clicked );
83   connect( mGreaterEqualButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mGreaterEqualButton_clicked );
84   connect( mOpenBracketPushButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mOpenBracketPushButton_clicked );
85   connect( mCloseBracketPushButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mCloseBracketPushButton_clicked );
86   connect( mEqualButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mEqualButton_clicked );
87   connect( mNotEqualButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mNotEqualButton_clicked );
88   connect( mMinButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mMinButton_clicked );
89   connect( mMaxButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mMaxButton_clicked );
90   connect( mAbsButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mAbsButton_clicked );
91   connect( mPowButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mPowButton_clicked );
92   connect( mIfButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mIfButton_clicked );
93   connect( mAndButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mAndButton_clicked );
94   connect( mOrButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mOrButton_clicked );
95   connect( mNotButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mNotButton_clicked );
96   connect( mSumAggrButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mSumAggrButton_clicked );
97   connect( mMaxAggrButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mMaxAggrButton_clicked );
98   connect( mMinAggrButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mMinAggrButton_clicked );
99   connect( mAverageAggrButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mAverageAggrButton_clicked );
100   connect( mNoDataButton, &QPushButton::clicked, this, &QgsMeshCalculatorDialog::mNoDataButton_clicked );
101 
102   mExpressionTextEdit->setCurrentFont( QFontDatabase::systemFont( QFontDatabase::FixedFont ) );
103 
104   useFullLayerExtent();
105   repopulateTimeCombos();
106   mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
107   connect( mButtonBox, &QDialogButtonBox::helpRequested, this, [ = ]
108   {
109     QgsHelp::openHelp( QStringLiteral( "working_with_mesh/mesh_properties.html#mesh-calculator" ) );
110   } );
111 
112   const QgsSettings settings;
113   mOutputDatasetFileWidget->setStorageMode( QgsFileWidget::SaveFile );
114   mOutputDatasetFileWidget->setDialogTitle( tr( "Enter Mesh Dataset File" ) );
115   mOutputDatasetFileWidget->setDefaultRoot( settings.value( QStringLiteral( "/MeshCalculator/lastOutputDir" ), QDir::homePath() ).toString() );
116   onOutputFormatChange();
117   connect( mOutputDatasetFileWidget, &QgsFileWidget::fileChanged, this, &QgsMeshCalculatorDialog::updateInfoMessage );
118 
119   connect( mUseVirtualProviderCheckBox, &QCheckBox::clicked, this, &QgsMeshCalculatorDialog::onVirtualCheckboxChange );
120   onVirtualCheckboxChange();
121 }
122 
123 QgsMeshCalculatorDialog::~QgsMeshCalculatorDialog() = default;
124 
125 
formulaString() const126 QString QgsMeshCalculatorDialog::formulaString() const
127 {
128   return mExpressionTextEdit->toPlainText();
129 }
130 
meshLayer() const131 QgsMeshLayer *QgsMeshCalculatorDialog::meshLayer() const
132 {
133   return mLayer;
134 }
135 
outputFile() const136 QString QgsMeshCalculatorDialog::outputFile() const
137 {
138   const QString ret = mOutputDatasetFileWidget->filePath();
139   return controlSuffix( ret );
140 }
141 
outputExtent() const142 QgsRectangle QgsMeshCalculatorDialog::outputExtent() const
143 {
144   const QgsRectangle ret(
145     mXMinSpinBox->value(),
146     mYMinSpinBox->value(),
147     mXMaxSpinBox->value(),
148     mYMaxSpinBox->value()
149   );
150   return ret;
151 }
152 
maskGeometry() const153 QgsGeometry QgsMeshCalculatorDialog::maskGeometry() const
154 {
155   QgsVectorLayer *mask_layer = qobject_cast<QgsVectorLayer *> ( cboLayerMask->currentLayer() );
156   if ( mask_layer )
157   {
158     return maskGeometry( mask_layer );
159   }
160   return QgsGeometry();
161 }
162 
driver() const163 QString QgsMeshCalculatorDialog::driver() const
164 {
165   return mOutputFormatComboBox->currentData().toString();
166 }
167 
groupName() const168 QString QgsMeshCalculatorDialog::groupName() const
169 {
170   return mOutputGroupNameLineEdit->text();
171 }
172 
maskGeometry(QgsVectorLayer * layer) const173 QgsGeometry QgsMeshCalculatorDialog::maskGeometry( QgsVectorLayer *layer ) const
174 {
175   QgsFeatureIterator it = layer->getFeatures();
176   QVector<QgsGeometry> geometries;
177   QgsFeature feat;
178   while ( it.nextFeature( feat ) )
179   {
180     geometries.push_back( feat.geometry() );
181   }
182   const QgsGeometry ret = QgsGeometry::unaryUnion( geometries ) ;
183   return ret;
184 }
185 
startTime() const186 double QgsMeshCalculatorDialog::startTime() const
187 {
188   if ( mStartTimeComboBox->currentIndex() > -1 )
189     return mStartTimeComboBox->itemData( mStartTimeComboBox->currentIndex() ).toDouble();
190   else
191     return 0;
192 }
193 
endTime() const194 double QgsMeshCalculatorDialog::endTime() const
195 {
196   if ( mEndTimeComboBox->currentIndex() > -1 )
197     return mEndTimeComboBox->itemData( mEndTimeComboBox->currentIndex() ).toDouble();
198   else
199     return 0;
200 }
201 
calculator() const202 std::unique_ptr<QgsMeshCalculator> QgsMeshCalculatorDialog::calculator() const
203 {
204   std::unique_ptr<QgsMeshCalculator> calc;
205   QgsMeshDatasetGroup::Type destination = QgsMeshDatasetGroup::Persistent;
206 
207   if ( mUseVirtualProviderCheckBox->isChecked() )
208     destination = QgsMeshDatasetGroup::Virtual;
209 
210   switch ( destination )
211   {
212     case QgsMeshDatasetGroup::Persistent:
213       if ( useExtentCb->isChecked() )
214       {
215         calc.reset(
216           new QgsMeshCalculator(
217             formulaString(),
218             driver(),
219             groupName(),
220             outputFile(),
221             outputExtent(),
222             startTime(),
223             endTime(),
224             meshLayer()
225           )
226         );
227       }
228       else
229       {
230         calc.reset(
231           new QgsMeshCalculator(
232             formulaString(),
233             driver(),
234             groupName(),
235             outputFile(),
236             maskGeometry(),
237             startTime(),
238             endTime(),
239             meshLayer()
240           )
241         );
242       }
243       break;
244     case QgsMeshDatasetGroup::Virtual:
245       if ( useExtentCb->isChecked() )
246       {
247         calc.reset(
248           new QgsMeshCalculator(
249             formulaString(),
250             groupName(),
251             outputExtent(),
252             destination,
253             meshLayer(),
254             startTime(),
255             endTime()
256           )
257         );
258       }
259       else
260       {
261         calc.reset(
262           new QgsMeshCalculator(
263             formulaString(),
264             groupName(),
265             maskGeometry(),
266             destination,
267             meshLayer(),
268             startTime(),
269             endTime()
270           )
271         );
272       }
273       break;
274     default:
275       break;
276   }
277 
278   return calc;
279 }
280 
datasetGroupEntry(const QModelIndex & index)281 void QgsMeshCalculatorDialog::datasetGroupEntry( const QModelIndex &index )
282 {
283   const QString group = quoteDatasetGroupEntry( datasetGroupName( index ) );
284   mExpressionTextEdit->insertPlainText( QStringLiteral( " %1 " ).arg( group ) );
285 }
286 
toggleExtendMask()287 void QgsMeshCalculatorDialog::toggleExtendMask()
288 {
289   bool visible = useMaskCb->isChecked();
290   extendBox->setVisible( !visible );
291   maskBox->setVisible( visible );
292 }
293 
updateInfoMessage()294 void QgsMeshCalculatorDialog::updateInfoMessage()
295 {
296   QgsMeshDriverMetadata::MeshDriverCapability requiredCapability;
297 
298   // expression is valid
299   const QgsMeshCalculator::Result result = QgsMeshCalculator::expressionIsValid(
300         formulaString(),
301         meshLayer(),
302         requiredCapability
303       );
304   const bool expressionValid = result == QgsMeshCalculator::Success;
305 
306   // selected driver is appropriate
307   const bool notInFile = mUseVirtualProviderCheckBox->isChecked();
308   bool driverValid = false;
309   if ( expressionValid )
310   {
311     const QString driverKey = driver();
312     if ( mMeshDrivers.contains( driverKey ) )
313     {
314       const QgsMeshDriverMetadata meta = mMeshDrivers[driverKey];
315       driverValid = meta.capabilities().testFlag( requiredCapability );
316     }
317   }
318   else
319   {
320     // can't determine if driver is valid when expression is invalid
321     driverValid = true;
322   }
323 
324   // output path is selected
325   bool filePathValid = false;
326   QString outputPath = outputFile();
327   if ( !outputPath.isEmpty() )
328   {
329     outputPath = QFileInfo( outputPath ).absolutePath();
330     filePathValid = QFileInfo( outputPath ).isWritable();
331   }
332 
333   // group name
334   const bool groupNameValid = !groupName().isEmpty() && !mVariableNames.contains( groupName() );
335 
336   if ( expressionValid &&
337        ( notInFile || ( driverValid && filePathValid ) )  &&
338        groupNameValid )
339   {
340     mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( true );
341     mExpressionValidLabel->setText( tr( "Expression valid" ) );
342   }
343   else
344   {
345     mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
346     if ( !expressionValid )
347       mExpressionValidLabel->setText( tr( "Expression invalid" ) );
348     else if ( !filePathValid && !notInFile )
349       mExpressionValidLabel->setText( tr( "Invalid file path" ) );
350     else if ( !driverValid && !notInFile )
351       mExpressionValidLabel->setText( tr( "Selected driver cannot store data defined on %1" ).arg( requiredCapability == QgsMeshDriverMetadata::CanWriteFaceDatasets ? tr( " faces " ) : tr( " vertices " ) ) );
352     else if ( !groupNameValid )
353       mExpressionValidLabel->setText( tr( "Invalid group name" ) );
354   }
355 }
356 
onVirtualCheckboxChange()357 void QgsMeshCalculatorDialog::onVirtualCheckboxChange()
358 {
359   mOutputDatasetFileWidget->setVisible( !mUseVirtualProviderCheckBox->isChecked() );
360   mOutputDatasetFileLabel->setVisible( !mUseVirtualProviderCheckBox->isChecked() );
361   mOutputFormatComboBox->setVisible( !mUseVirtualProviderCheckBox->isChecked() );
362   mOutputFormatLabel->setVisible( !mUseVirtualProviderCheckBox->isChecked() );
363   updateInfoMessage();
364 }
365 
onOutputFormatChange()366 void QgsMeshCalculatorDialog::onOutputFormatChange()
367 {
368   const QString suffix = currentOutputSuffix();
369   if ( !suffix.isEmpty() )
370   {
371     QString filter = mOutputFormatComboBox->currentText();
372     filter.append( QStringLiteral( " (*.%1)" ).arg( suffix ) );
373     mOutputDatasetFileWidget->setFilter( filter );
374   }
375 }
376 
datasetGroupName(const QModelIndex & index) const377 QString QgsMeshCalculatorDialog::datasetGroupName( const QModelIndex &index ) const
378 {
379   if ( !index.isValid() )
380     return QString();
381 
382   return index.data( Qt::DisplayRole ).toString();
383 }
384 
mCurrentLayerExtentButton_clicked()385 void QgsMeshCalculatorDialog::mCurrentLayerExtentButton_clicked()
386 {
387   useFullLayerExtent();
388 }
389 
mAllTimesButton_clicked()390 void QgsMeshCalculatorDialog::mAllTimesButton_clicked()
391 {
392   useAllTimesFromLayer();
393 }
394 
mPlusPushButton_clicked()395 void QgsMeshCalculatorDialog::mPlusPushButton_clicked()
396 {
397   mExpressionTextEdit->insertPlainText( QStringLiteral( " + " ) );
398 }
399 
mMinusPushButton_clicked()400 void QgsMeshCalculatorDialog::mMinusPushButton_clicked()
401 {
402   mExpressionTextEdit->insertPlainText( QStringLiteral( " - " ) );
403 }
404 
mLessButton_clicked()405 void QgsMeshCalculatorDialog::mLessButton_clicked()
406 {
407   mExpressionTextEdit->insertPlainText( QStringLiteral( " < " ) );
408 }
409 
mLesserEqualButton_clicked()410 void QgsMeshCalculatorDialog::mLesserEqualButton_clicked()
411 {
412   mExpressionTextEdit->insertPlainText( QStringLiteral( " <= " ) );
413 }
414 
mMultiplyPushButton_clicked()415 void QgsMeshCalculatorDialog::mMultiplyPushButton_clicked()
416 {
417   mExpressionTextEdit->insertPlainText( QStringLiteral( " * " ) );
418 }
419 
mDividePushButton_clicked()420 void QgsMeshCalculatorDialog::mDividePushButton_clicked()
421 {
422   mExpressionTextEdit->insertPlainText( QStringLiteral( " / " ) );
423 }
424 
mGreaterButton_clicked()425 void QgsMeshCalculatorDialog::mGreaterButton_clicked()
426 {
427   mExpressionTextEdit->insertPlainText( QStringLiteral( " > " ) );
428 }
429 
mGreaterEqualButton_clicked()430 void QgsMeshCalculatorDialog::mGreaterEqualButton_clicked()
431 {
432   mExpressionTextEdit->insertPlainText( QStringLiteral( " >= " ) );
433 }
434 
mOpenBracketPushButton_clicked()435 void QgsMeshCalculatorDialog::mOpenBracketPushButton_clicked()
436 {
437   mExpressionTextEdit->insertPlainText( QStringLiteral( " ( " ) );
438 }
439 
mCloseBracketPushButton_clicked()440 void QgsMeshCalculatorDialog::mCloseBracketPushButton_clicked()
441 {
442   mExpressionTextEdit->insertPlainText( QStringLiteral( " ) " ) );
443 }
444 
mEqualButton_clicked()445 void QgsMeshCalculatorDialog::mEqualButton_clicked()
446 {
447   mExpressionTextEdit->insertPlainText( QStringLiteral( " = " ) );
448 }
449 
mNotEqualButton_clicked()450 void QgsMeshCalculatorDialog::mNotEqualButton_clicked()
451 {
452   mExpressionTextEdit->insertPlainText( QStringLiteral( " != " ) );
453 }
454 
mMinButton_clicked()455 void QgsMeshCalculatorDialog::mMinButton_clicked()
456 {
457   mExpressionTextEdit->insertPlainText( QStringLiteral( " min ( A , B ) " ) );
458 }
459 
mMaxButton_clicked()460 void QgsMeshCalculatorDialog::mMaxButton_clicked()
461 {
462   mExpressionTextEdit->insertPlainText( QStringLiteral( " max ( A , B ) " ) );
463 }
464 
mAbsButton_clicked()465 void QgsMeshCalculatorDialog::mAbsButton_clicked()
466 {
467   mExpressionTextEdit->insertPlainText( QStringLiteral( " abs ( " ) );
468 }
469 
mPowButton_clicked()470 void QgsMeshCalculatorDialog::mPowButton_clicked()
471 {
472   mExpressionTextEdit->insertPlainText( QStringLiteral( " ^ " ) );
473 }
474 
mIfButton_clicked()475 void QgsMeshCalculatorDialog::mIfButton_clicked()
476 {
477   mExpressionTextEdit->insertPlainText( QStringLiteral( " if ( 1 = 1 , NODATA , NODATA ) " ) );
478 }
479 
mAndButton_clicked()480 void QgsMeshCalculatorDialog::mAndButton_clicked()
481 {
482   mExpressionTextEdit->insertPlainText( QStringLiteral( " and " ) );
483 }
484 
mOrButton_clicked()485 void QgsMeshCalculatorDialog::mOrButton_clicked()
486 {
487   mExpressionTextEdit->insertPlainText( QStringLiteral( " or " ) );
488 }
489 
mNotButton_clicked()490 void QgsMeshCalculatorDialog::mNotButton_clicked()
491 {
492   mExpressionTextEdit->insertPlainText( QStringLiteral( " not " ) );
493 }
494 
mSumAggrButton_clicked()495 void QgsMeshCalculatorDialog::mSumAggrButton_clicked()
496 {
497   mExpressionTextEdit->insertPlainText( QStringLiteral( " sum_aggr ( " ) );
498 }
499 
mMaxAggrButton_clicked()500 void QgsMeshCalculatorDialog::mMaxAggrButton_clicked()
501 {
502   mExpressionTextEdit->insertPlainText( QStringLiteral( " max_aggr ( " ) );
503 }
504 
mMinAggrButton_clicked()505 void QgsMeshCalculatorDialog::mMinAggrButton_clicked()
506 {
507   mExpressionTextEdit->insertPlainText( QStringLiteral( " min_aggr ( " ) );
508 }
509 
mAverageAggrButton_clicked()510 void QgsMeshCalculatorDialog::mAverageAggrButton_clicked()
511 {
512   mExpressionTextEdit->insertPlainText( QStringLiteral( " average_aggr ( " ) );
513 }
514 
mNoDataButton_clicked()515 void QgsMeshCalculatorDialog::mNoDataButton_clicked()
516 {
517   mExpressionTextEdit->insertPlainText( QStringLiteral( " NODATA " ) );
518 }
519 
quoteDatasetGroupEntry(const QString group)520 QString QgsMeshCalculatorDialog::quoteDatasetGroupEntry( const QString group )
521 {
522   QString ret( group );
523   ret = QStringLiteral( "\"%1\"" ).arg( ret.replace( "\"", "\\\"" ) );
524   return ret;
525 }
526 
controlSuffix(const QString & fileName) const527 QString QgsMeshCalculatorDialog::controlSuffix( const QString &fileName ) const
528 {
529   if ( fileName.isEmpty() )
530     return fileName;
531 
532   const QFileInfo fileInfo( fileName );
533 
534   const QString appropriateSuffix = currentOutputSuffix();
535 
536   const QString existingSuffix = fileInfo.suffix();
537   if ( !( existingSuffix.isEmpty() && appropriateSuffix.isEmpty() )
538        && existingSuffix != appropriateSuffix )
539   {
540     const int pos = fileName.lastIndexOf( '.' );
541     QString ret = fileName.left( pos + 1 );
542     ret.append( appropriateSuffix );
543 
544     return ret;
545   }
546 
547   return fileName;
548 }
549 
currentOutputSuffix() const550 QString QgsMeshCalculatorDialog::currentOutputSuffix() const
551 {
552   const QString currentDriver = mOutputFormatComboBox->currentData().toString();
553   QString suffix;
554   if ( mMeshDrivers.contains( currentDriver ) )
555     suffix = mMeshDrivers[currentDriver].writeDatasetOnFileSuffix();
556 
557   return suffix;
558 }
559 
getMeshDrivers()560 void QgsMeshCalculatorDialog::getMeshDrivers()
561 {
562   QgsProviderMetadata *providerMetadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "mdal" ) );
563   if ( providerMetadata )
564   {
565     const QList<QgsMeshDriverMetadata> allDrivers = providerMetadata->meshDriversMetadata();
566     for ( const QgsMeshDriverMetadata &meta : allDrivers )
567     {
568       if ( meta.capabilities().testFlag( QgsMeshDriverMetadata::MeshDriverCapability::CanWriteFaceDatasets ) ||
569            meta.capabilities().testFlag( QgsMeshDriverMetadata::MeshDriverCapability::CanWriteEdgeDatasets ) ||
570            meta.capabilities().testFlag( QgsMeshDriverMetadata::MeshDriverCapability::CanWriteVertexDatasets ) )
571         mMeshDrivers[meta.name()] = meta;
572     }
573   }
574 }
575 
populateDriversComboBox()576 void QgsMeshCalculatorDialog::populateDriversComboBox( )
577 {
578 
579   whileBlocking( mOutputFormatComboBox )->clear();
580 
581   const QList< QgsMeshDriverMetadata > vals = mMeshDrivers.values();
582   for ( const QgsMeshDriverMetadata &meta : vals )
583   {
584     whileBlocking( mOutputFormatComboBox )->addItem( meta.description(), meta.name() );
585   }
586   mOutputFormatComboBox->setCurrentIndex( 0 );
587 }
588 
useFullLayerExtent()589 void QgsMeshCalculatorDialog::useFullLayerExtent()
590 {
591   QgsMeshLayer *layer = meshLayer();
592   if ( !layer )
593     return;
594 
595   const QgsRectangle layerExtent = layer->extent();
596   mXMinSpinBox->setValue( layerExtent.xMinimum() );
597   mXMaxSpinBox->setValue( layerExtent.xMaximum() );
598   mYMinSpinBox->setValue( layerExtent.yMinimum() );
599   mYMaxSpinBox->setValue( layerExtent.yMaximum() );
600 }
601 
useAllTimesFromLayer()602 void QgsMeshCalculatorDialog::useAllTimesFromLayer()
603 {
604   const QString datasetGroupName = currentDatasetGroup();
605   setTimesByDatasetGroupName( datasetGroupName );
606 }
607 
currentDatasetGroup() const608 QString QgsMeshCalculatorDialog::currentDatasetGroup() const
609 {
610   const QModelIndex index = mDatasetsListWidget->currentIndex();
611 
612   if ( !index.isValid() )
613     return QString();
614 
615   return datasetGroupName( index );
616 }
617 
setTimesByDatasetGroupName(const QString group)618 void QgsMeshCalculatorDialog::setTimesByDatasetGroupName( const QString group )
619 {
620   QgsMeshLayer *layer = meshLayer();
621   if ( !layer || !layer->dataProvider() )
622     return;
623   const QgsMeshDataProvider *dp = layer->dataProvider();
624 
625   // find group index from group name
626   int groupIndex = -1;
627   for ( int i = 0; i < dp->datasetGroupCount(); ++i )
628   {
629     const QgsMeshDatasetGroupMetadata meta = dp->datasetGroupMetadata( i );
630     if ( meta.name() == group )
631     {
632       groupIndex = i;
633       break;
634     }
635   }
636 
637   if ( groupIndex < 0 )
638     return; //not found
639 
640   const int datasetCount = dp->datasetCount( groupIndex );
641   if ( datasetCount < 1 )
642     return; // group without datasets
643 
644 
645   // find maximum and minimum time in this group
646   const double minTime = dp->datasetMetadata( QgsMeshDatasetIndex( groupIndex, 0 ) ).time();
647   int idx = mStartTimeComboBox->findData( minTime );
648   if ( idx >= 0 )
649     mStartTimeComboBox->setCurrentIndex( idx );
650 
651   const double maxTime = dp->datasetMetadata( QgsMeshDatasetIndex( groupIndex, datasetCount - 1 ) ).time();
652   idx = mEndTimeComboBox->findData( maxTime );
653   if ( idx >= 0 )
654     mEndTimeComboBox->setCurrentIndex( idx );
655 }
656 
repopulateTimeCombos()657 void QgsMeshCalculatorDialog::repopulateTimeCombos()
658 {
659   QgsMeshLayer *layer = meshLayer();
660   if ( !layer || !layer->dataProvider() )
661     return;
662   const QgsMeshDataProvider *dp = layer->dataProvider();
663 
664   // extract all times from all datasets
665   QMap<QString, double> times;
666 
667   for ( int groupIndex = 0; groupIndex < dp->datasetGroupCount(); ++groupIndex )
668   {
669     for ( int datasetIndex = 0; datasetIndex < dp->datasetCount( groupIndex ); ++datasetIndex )
670     {
671       const QgsMeshDatasetMetadata meta = dp->datasetMetadata( QgsMeshDatasetIndex( groupIndex, datasetIndex ) );
672       const double time = meta.time();
673       const QString timestr = layer->formatTime( time );
674 
675       times[timestr] = time;
676     }
677   }
678 
679   // sort by text
680   auto keys = times.keys();
681   keys.sort();
682 
683   mStartTimeComboBox->blockSignals( true );
684   mEndTimeComboBox->blockSignals( true );
685   mStartTimeComboBox->clear();
686   mEndTimeComboBox->clear();
687 
688   // populate combos
689   for ( const QString &key : keys )
690   {
691     mStartTimeComboBox->addItem( key, times[key] );
692     mEndTimeComboBox->addItem( key, times[key] );
693   }
694 
695   mStartTimeComboBox->blockSignals( false );
696   mEndTimeComboBox->blockSignals( false );
697 
698 
699   if ( !times.empty() )
700   {
701     mStartTimeComboBox->setCurrentIndex( 0 );
702     mEndTimeComboBox->setCurrentIndex( times.size() - 1 );
703   }
704 }
705