1 /***************************************************************************
2                           qgsrasterformatsaveoptionswidget.cpp
3                              -------------------
4     begin                : July 2012
5     copyright            : (C) 2012 by Etienne Tourigny
6     email                : etourigny dot dev at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgsrasterformatsaveoptionswidget.h"
19 #include "qgslogger.h"
20 #include "qgsdialog.h"
21 #include "qgsrasterlayer.h"
22 #include "qgsproviderregistry.h"
23 #include "qgsrasterdataprovider.h"
24 #include "qgssettings.h"
25 #include "qgsgdalutils.h"
26 
27 #include <QInputDialog>
28 #include <QMessageBox>
29 #include <QTextEdit>
30 #include <QMouseEvent>
31 #include <QMenu>
32 
33 
34 QMap< QString, QStringList > QgsRasterFormatSaveOptionsWidget::sBuiltinProfiles;
35 
36 static const QString PYRAMID_JPEG_YCBCR_COMPRESSION( QStringLiteral( "JPEG_QUALITY_OVERVIEW=75 COMPRESS_OVERVIEW=JPEG PHOTOMETRIC_OVERVIEW=YCBCR INTERLEAVE_OVERVIEW=PIXEL" ) );
37 static const QString PYRAMID_JPEG_COMPRESSION( QStringLiteral( "JPEG_QUALITY_OVERVIEW=75 COMPRESS_OVERVIEW=JPEG INTERLEAVE_OVERVIEW=PIXEL" ) );
38 
QgsRasterFormatSaveOptionsWidget(QWidget * parent,const QString & format,QgsRasterFormatSaveOptionsWidget::Type type,const QString & provider)39 QgsRasterFormatSaveOptionsWidget::QgsRasterFormatSaveOptionsWidget( QWidget *parent, const QString &format,
40     QgsRasterFormatSaveOptionsWidget::Type type, const QString &provider )
41   : QWidget( parent )
42   , mFormat( format )
43   , mProvider( provider )
44 {
45   setupUi( this );
46   setMinimumSize( this->fontMetrics().height() * 5, 240 );
47 
48   connect( mProfileNewButton, &QPushButton::clicked, this, &QgsRasterFormatSaveOptionsWidget::mProfileNewButton_clicked );
49   connect( mProfileDeleteButton, &QPushButton::clicked, this, &QgsRasterFormatSaveOptionsWidget::mProfileDeleteButton_clicked );
50   connect( mProfileResetButton, &QPushButton::clicked, this, &QgsRasterFormatSaveOptionsWidget::mProfileResetButton_clicked );
51   connect( mOptionsAddButton, &QPushButton::clicked, this, &QgsRasterFormatSaveOptionsWidget::mOptionsAddButton_clicked );
52   connect( mOptionsDeleteButton, &QPushButton::clicked, this, &QgsRasterFormatSaveOptionsWidget::mOptionsDeleteButton_clicked );
53   connect( mOptionsLineEdit, &QLineEdit::editingFinished, this, &QgsRasterFormatSaveOptionsWidget::mOptionsLineEdit_editingFinished );
54 
55   setType( type );
56 
57   if ( sBuiltinProfiles.isEmpty() )
58   {
59     // key=profileKey values=format,profileName,options
60     sBuiltinProfiles[ QStringLiteral( "z_adefault" )] = ( QStringList() << QString() << tr( "Default" ) << QString() );
61 
62     // these GTiff profiles are based on Tim's benchmarks at
63     // http://linfiniti.com/2011/05/gdal-efficiency-of-various-compression-algorithms/
64     // big: no compression | medium: reasonable size/speed tradeoff | small: smallest size
65     sBuiltinProfiles[ QStringLiteral( "z_gtiff_1big" )] =
66       ( QStringList() << QStringLiteral( "GTiff" ) << tr( "No Compression" )
67         << QStringLiteral( "COMPRESS=NONE BIGTIFF=IF_NEEDED" ) );
68     sBuiltinProfiles[ QStringLiteral( "z_gtiff_2medium" )] =
69       ( QStringList() << QStringLiteral( "GTiff" ) << tr( "Low Compression" )
70         << QStringLiteral( "COMPRESS=PACKBITS" ) );
71     sBuiltinProfiles[ QStringLiteral( "z_gtiff_3small" )] =
72       ( QStringList() << QStringLiteral( "GTiff" ) << tr( "High Compression" )
73         << QStringLiteral( "COMPRESS=DEFLATE PREDICTOR=2 ZLEVEL=9" ) );
74     sBuiltinProfiles[ QStringLiteral( "z_gtiff_4jpeg" )] =
75       ( QStringList() << QStringLiteral( "GTiff" ) << tr( "JPEG Compression" )
76         << QStringLiteral( "COMPRESS=JPEG JPEG_QUALITY=75" ) );
77 
78     // overview compression schemes for GTiff format, see
79     // http://www.gdal.org/gdaladdo.html and http://www.gdal.org/frmt_gtiff.html
80     // TODO - should we offer GDAL_TIFF_OVR_BLOCKSIZE option here or in QgsRasterPyramidsOptionsWidget ?
81     sBuiltinProfiles[ QStringLiteral( "z__pyramids_gtiff_1big" )] =
82       ( QStringList() << QStringLiteral( "_pyramids" ) << tr( "No Compression" )
83         << QStringLiteral( "COMPRESS_OVERVIEW=NONE BIGTIFF_OVERVIEW=IF_NEEDED" ) );
84     sBuiltinProfiles[ QStringLiteral( "z__pyramids_gtiff_2medium" )] =
85       ( QStringList() << QStringLiteral( "_pyramids" ) << tr( "Low Compression" )
86         << QStringLiteral( "COMPRESS_OVERVIEW=PACKBITS" ) );
87     sBuiltinProfiles[ QStringLiteral( "z__pyramids_gtiff_3small" )] =
88       ( QStringList() << QStringLiteral( "_pyramids" ) << tr( "High Compression" )
89         << QStringLiteral( "COMPRESS_OVERVIEW=DEFLATE PREDICTOR_OVERVIEW=2 ZLEVEL=9" ) ); // how to set zlevel?
90     sBuiltinProfiles[ QStringLiteral( "z__pyramids_gtiff_4jpeg" )] =
91       ( QStringList() << QStringLiteral( "_pyramids" ) << tr( "JPEG Compression" )
92         << PYRAMID_JPEG_YCBCR_COMPRESSION );
93   }
94 
95   connect( mProfileComboBox, &QComboBox::currentTextChanged,
96            this, &QgsRasterFormatSaveOptionsWidget::updateOptions );
97   connect( mOptionsTable, &QTableWidget::cellChanged, this, &QgsRasterFormatSaveOptionsWidget::optionsTableChanged );
98   connect( mOptionsHelpButton, &QAbstractButton::clicked, this, &QgsRasterFormatSaveOptionsWidget::helpOptions );
99   connect( mOptionsValidateButton, &QAbstractButton::clicked, this, [ = ] { validateOptions(); } );
100 
101   // create eventFilter to map right click to swapOptionsUI()
102   // mOptionsLabel->installEventFilter( this );
103   mOptionsLineEdit->installEventFilter( this );
104   mOptionsStackedWidget->installEventFilter( this );
105 
106   updateControls();
107   updateProfiles();
108 
109   QgsDebugMsg( QStringLiteral( "done" ) );
110 }
111 
setFormat(const QString & format)112 void QgsRasterFormatSaveOptionsWidget::setFormat( const QString &format )
113 {
114   mFormat = format;
115   updateControls();
116   updateProfiles();
117 }
118 
setProvider(const QString & provider)119 void QgsRasterFormatSaveOptionsWidget::setProvider( const QString &provider )
120 {
121   mProvider = provider;
122   updateControls();
123 }
124 
125 // show/hide widgets - we need this function if widget is used in creator
setType(QgsRasterFormatSaveOptionsWidget::Type type)126 void QgsRasterFormatSaveOptionsWidget::setType( QgsRasterFormatSaveOptionsWidget::Type type )
127 {
128   const QList< QWidget * > widgets = this->findChildren<QWidget *>();
129   if ( ( type == Table ) || ( type == LineEdit ) )
130   {
131     // hide all controls, except stacked widget
132     const auto constWidgets = widgets;
133     for ( QWidget *widget : constWidgets )
134       widget->setVisible( false );
135     mOptionsStackedWidget->setVisible( true );
136     const auto children { mOptionsStackedWidget->findChildren<QWidget *>() };
137     for ( QWidget *widget : children )
138       widget->setVisible( true );
139 
140     // show relevant page
141     if ( type == Table )
142       swapOptionsUI( 0 );
143     else if ( type == LineEdit )
144       swapOptionsUI( 1 );
145   }
146   else
147   {
148     // show all widgets, except profile buttons (unless Full)
149     const auto constWidgets = widgets;
150     for ( QWidget *widget : constWidgets )
151       widget->setVisible( true );
152     if ( type != Full )
153       mProfileButtons->setVisible( false );
154 
155     // show elevant page
156     if ( type == ProfileLineEdit )
157       swapOptionsUI( 1 );
158   }
159 }
160 
pseudoFormat() const161 QString QgsRasterFormatSaveOptionsWidget::pseudoFormat() const
162 {
163   return mPyramids ? QStringLiteral( "_pyramids" ) : mFormat;
164 }
165 
updateProfiles()166 void QgsRasterFormatSaveOptionsWidget::updateProfiles()
167 {
168   // build profiles list = user + builtin(last)
169   const QString format = pseudoFormat();
170   QStringList profileKeys = profiles();
171   QMapIterator<QString, QStringList> it( sBuiltinProfiles );
172   while ( it.hasNext() )
173   {
174     it.next();
175     const QString profileKey = it.key();
176     if ( ! profileKeys.contains( profileKey ) && !it.value().isEmpty() )
177     {
178       // insert key if is for all formats or this format (GTiff)
179       if ( it.value()[0].isEmpty() ||  it.value()[0] == format )
180       {
181         profileKeys.insert( 0, profileKey );
182       }
183     }
184   }
185   std::sort( profileKeys.begin(), profileKeys.end() );
186 
187   // populate mOptionsMap and mProfileComboBox
188   mOptionsMap.clear();
189   mProfileComboBox->blockSignals( true );
190   mProfileComboBox->clear();
191   const auto constProfileKeys = profileKeys;
192   for ( const QString &profileKey : constProfileKeys )
193   {
194     QString profileName, profileOptions;
195     profileOptions = createOptions( profileKey );
196     if ( sBuiltinProfiles.contains( profileKey ) )
197     {
198       profileName = sBuiltinProfiles[ profileKey ][ 1 ];
199       if ( profileOptions.isEmpty() )
200         profileOptions = sBuiltinProfiles[ profileKey ][ 2 ];
201     }
202     else
203     {
204       profileName = profileKey;
205     }
206     mOptionsMap[ profileKey ] = profileOptions;
207     mProfileComboBox->addItem( profileName, profileKey );
208   }
209 
210   // update UI
211   mProfileComboBox->blockSignals( false );
212   // mProfileComboBox->setCurrentIndex( 0 );
213   const QgsSettings mySettings;
214   mProfileComboBox->setCurrentIndex( mProfileComboBox->findData( mySettings.value(
215                                        mProvider + "/driverOptions/" + format.toLower() + "/defaultProfile",
216                                        "z_adefault" ) ) );
217   updateOptions();
218 }
219 
updateOptions()220 void QgsRasterFormatSaveOptionsWidget::updateOptions()
221 {
222   mBlockOptionUpdates++;
223   QString myOptions = mOptionsMap.value( currentProfileKey() );
224 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
225   QStringList myOptionsList = myOptions.trimmed().split( ' ', QString::SkipEmptyParts );
226 #else
227   QStringList myOptionsList = myOptions.trimmed().split( ' ', Qt::SkipEmptyParts );
228 #endif
229 
230   // If the default JPEG compression profile was selected, remove PHOTOMETRIC_OVERVIEW=YCBCR
231   // if the raster is not RGB. Otherwise this is bound to fail afterwards.
232   if ( mRasterLayer && mRasterLayer->bandCount() != 3 &&
233        myOptions == PYRAMID_JPEG_YCBCR_COMPRESSION )
234   {
235     myOptions = PYRAMID_JPEG_COMPRESSION;
236   }
237 
238   if ( mOptionsStackedWidget->currentIndex() == 0 )
239   {
240     mOptionsTable->setRowCount( 0 );
241     for ( int i = 0; i < myOptionsList.count(); i++ )
242     {
243       QStringList key_value = myOptionsList[i].split( '=' );
244       if ( key_value.count() == 2 )
245       {
246         mOptionsTable->insertRow( i );
247         mOptionsTable->setItem( i, 0, new QTableWidgetItem( key_value[0] ) );
248         mOptionsTable->setItem( i, 1, new QTableWidgetItem( key_value[1] ) );
249       }
250     }
251   }
252   else
253   {
254     mOptionsLineEdit->setText( myOptions );
255     mOptionsLineEdit->setCursorPosition( 0 );
256   }
257 
258   mBlockOptionUpdates--;
259   emit optionsChanged();
260 }
261 
apply()262 void QgsRasterFormatSaveOptionsWidget::apply()
263 {
264   setCreateOptions();
265 }
266 
helpOptions()267 void QgsRasterFormatSaveOptionsWidget::helpOptions()
268 {
269   QString message;
270 
271   if ( mProvider == QLatin1String( "gdal" ) && !mFormat.isEmpty() && ! mPyramids )
272   {
273     message = QgsGdalUtils::helpCreationOptionsFormat( mFormat );
274     if ( message.isEmpty() )
275       message = tr( "Cannot get create options for driver %1" ).arg( mFormat );
276   }
277   else if ( mProvider == QLatin1String( "gdal" ) && mPyramids )
278   {
279     message = tr( "For details on pyramids options please see the following pages" );
280     message += QLatin1String( "\n\nhttps://gdal.org/programs/gdaladdo.html\n\nhttps://gdal.org/drivers/raster/gtiff.html" );
281   }
282   else
283     message = tr( "No help available" );
284 
285   // show simple non-modal dialog - should we make the basic xml prettier?
286   QgsDialog *dlg = new QgsDialog( this );
287   dlg->setWindowTitle( tr( "Create Options for %1" ).arg( mFormat ) );
288   QTextEdit *textEdit = new QTextEdit( dlg );
289   textEdit->setReadOnly( true );
290   // message = tr( "Create Options:\n\n%1" ).arg( message );
291   textEdit->setText( message );
292   dlg->layout()->addWidget( textEdit );
293   dlg->resize( 600, 400 );
294 #ifdef Q_OS_MAC
295   dlg->exec(); //modal
296 #else
297   dlg->show(); //non modal
298 #endif
299 }
300 
validateOptions(bool gui,bool reportOK)301 QString QgsRasterFormatSaveOptionsWidget::validateOptions( bool gui, bool reportOK )
302 {
303   const QStringList createOptions = options();
304   QString message;
305 
306   QgsDebugMsg( QStringLiteral( "layer: [%1] file: [%2] format: [%3]" ).arg( mRasterLayer ? mRasterLayer->id() : "none", mRasterFileName, mFormat ) );
307   // if no rasterLayer is defined, but we have a raster fileName, then create a temp. rasterLayer to validate options
308   // ideally we should keep it for future access, but this is trickier
309   QgsRasterLayer *rasterLayer = mRasterLayer;
310   bool tmpLayer = false;
311   if ( !( mRasterLayer && rasterLayer->dataProvider() ) && ! mRasterFileName.isNull() )
312   {
313     tmpLayer = true;
314     QgsRasterLayer::LayerOptions options;
315     options.skipCrsValidation = true;
316     rasterLayer = new QgsRasterLayer( mRasterFileName, QFileInfo( mRasterFileName ).baseName(), QStringLiteral( "gdal" ), options );
317   }
318 
319   if ( mProvider == QLatin1String( "gdal" ) && mPyramids )
320   {
321     if ( rasterLayer && rasterLayer->dataProvider() )
322     {
323       QgsDebugMsg( QStringLiteral( "calling validate pyramids on layer's data provider" ) );
324       message = rasterLayer->dataProvider()->validatePyramidsConfigOptions( mPyramidsFormat, createOptions, mFormat );
325     }
326     else
327     {
328       message = tr( "cannot validate pyramid options" );
329     }
330   }
331   else if ( !createOptions.isEmpty() && mProvider == QLatin1String( "gdal" ) && !mFormat.isEmpty() )
332   {
333     if ( rasterLayer && rasterLayer->dataProvider() )
334     {
335       QgsDebugMsg( QStringLiteral( "calling validate on layer's data provider" ) );
336       message = rasterLayer->dataProvider()->validateCreationOptions( createOptions, mFormat );
337     }
338     else
339     {
340       // get validateCreationOptionsFormat() function ptr for provider
341       message = QgsGdalUtils::validateCreationOptionsFormat( createOptions, mFormat );
342 
343     }
344   }
345   else if ( ! createOptions.isEmpty() )
346   {
347     QMessageBox::information( this, QString(), tr( "Cannot validate creation options." ), QMessageBox::Close );
348     if ( tmpLayer )
349       delete rasterLayer;
350     return QString();
351   }
352 
353   if ( gui )
354   {
355     if ( message.isNull() )
356     {
357       if ( reportOK )
358         QMessageBox::information( this, QString(), tr( "Valid" ), QMessageBox::Close );
359     }
360     else
361     {
362       QMessageBox::warning( this, QString(), tr( "Invalid %1:\n\n%2\n\nClick on help button to get valid creation options for this format." ).arg( mPyramids ? tr( "pyramid creation option" ) : tr( "creation option" ), message ), QMessageBox::Close );
363     }
364   }
365 
366   if ( tmpLayer )
367     delete rasterLayer;
368 
369   return message;
370 }
371 
optionsTableChanged()372 void QgsRasterFormatSaveOptionsWidget::optionsTableChanged()
373 {
374   if ( mBlockOptionUpdates )
375     return;
376 
377   QTableWidgetItem *key, *value;
378   QString options;
379   for ( int i = 0; i < mOptionsTable->rowCount(); i++ )
380   {
381     key = mOptionsTable->item( i, 0 );
382     if ( ! key  || key->text().isEmpty() )
383       continue;
384     value = mOptionsTable->item( i, 1 );
385     if ( ! value  || value->text().isEmpty() )
386       continue;
387     options += key->text() + '=' + value->text() + ' ';
388   }
389   options = options.trimmed();
390   mOptionsMap[ currentProfileKey()] = options;
391   mOptionsLineEdit->setText( options );
392   mOptionsLineEdit->setCursorPosition( 0 );
393 }
394 
mOptionsLineEdit_editingFinished()395 void QgsRasterFormatSaveOptionsWidget::mOptionsLineEdit_editingFinished()
396 {
397   mOptionsMap[ currentProfileKey()] = mOptionsLineEdit->text().trimmed();
398 }
399 
mProfileNewButton_clicked()400 void QgsRasterFormatSaveOptionsWidget::mProfileNewButton_clicked()
401 {
402   QString profileName = QInputDialog::getText( this, QString(), tr( "Profile name:" ) );
403   if ( ! profileName.isEmpty() )
404   {
405     profileName = profileName.trimmed();
406     mOptionsMap[ profileName ] = QString();
407     mProfileComboBox->addItem( profileName, profileName );
408     mProfileComboBox->setCurrentIndex( mProfileComboBox->count() - 1 );
409   }
410 }
411 
mProfileDeleteButton_clicked()412 void QgsRasterFormatSaveOptionsWidget::mProfileDeleteButton_clicked()
413 {
414   const int index = mProfileComboBox->currentIndex();
415   const QString profileKey = currentProfileKey();
416   if ( index != -1 && ! sBuiltinProfiles.contains( profileKey ) )
417   {
418     mOptionsMap.remove( profileKey );
419     mProfileComboBox->removeItem( index );
420   }
421 }
422 
mProfileResetButton_clicked()423 void QgsRasterFormatSaveOptionsWidget::mProfileResetButton_clicked()
424 {
425   const QString profileKey = currentProfileKey();
426   if ( sBuiltinProfiles.contains( profileKey ) )
427   {
428     mOptionsMap[ profileKey ] = sBuiltinProfiles[ profileKey ][ 2 ];
429   }
430   else
431   {
432     mOptionsMap[ profileKey ] = QString();
433   }
434   mOptionsLineEdit->setText( mOptionsMap.value( currentProfileKey() ) );
435   mOptionsLineEdit->setCursorPosition( 0 );
436   updateOptions();
437 }
438 
optionsTableEnableDeleteButton()439 void QgsRasterFormatSaveOptionsWidget::optionsTableEnableDeleteButton()
440 {
441   mOptionsDeleteButton->setEnabled( mOptionsTable->currentRow() >= 0 );
442 }
443 
mOptionsAddButton_clicked()444 void QgsRasterFormatSaveOptionsWidget::mOptionsAddButton_clicked()
445 {
446   mOptionsTable->insertRow( mOptionsTable->rowCount() );
447   // select the added row
448   const int newRow = mOptionsTable->rowCount() - 1;
449   QTableWidgetItem *item = new QTableWidgetItem();
450   mOptionsTable->setItem( newRow, 0, item );
451   mOptionsTable->setCurrentItem( item );
452 }
453 
mOptionsDeleteButton_clicked()454 void QgsRasterFormatSaveOptionsWidget::mOptionsDeleteButton_clicked()
455 {
456   if ( mOptionsTable->currentRow() >= 0 )
457   {
458     mOptionsTable->removeRow( mOptionsTable->currentRow() );
459     // select the previous row or the next one if there is no previous row
460     QTableWidgetItem *item = mOptionsTable->item( mOptionsTable->currentRow(), 0 );
461     mOptionsTable->setCurrentItem( item );
462     optionsTableChanged();
463   }
464 }
465 
settingsKey(QString profileName) const466 QString QgsRasterFormatSaveOptionsWidget::settingsKey( QString profileName ) const
467 {
468   if ( !profileName.isEmpty() )
469     profileName = "/profile_" + profileName;
470   else
471     profileName = "/profile_default" + profileName;
472   return mProvider + "/driverOptions/" + pseudoFormat().toLower() + profileName + "/create";
473 }
474 
currentProfileKey() const475 QString QgsRasterFormatSaveOptionsWidget::currentProfileKey() const
476 {
477   return mProfileComboBox->currentData().toString();
478 }
479 
options() const480 QStringList QgsRasterFormatSaveOptionsWidget::options() const
481 {
482 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
483   return mOptionsMap.value( currentProfileKey() ).trimmed().split( ' ', QString::SkipEmptyParts );
484 #else
485   return mOptionsMap.value( currentProfileKey() ).trimmed().split( ' ', Qt::SkipEmptyParts );
486 #endif
487 }
488 
createOptions(const QString & profileName) const489 QString QgsRasterFormatSaveOptionsWidget::createOptions( const QString &profileName ) const
490 {
491   const QgsSettings mySettings;
492   return mySettings.value( settingsKey( profileName ), "" ).toString();
493 }
494 
deleteCreateOptions(const QString & profileName)495 void QgsRasterFormatSaveOptionsWidget::deleteCreateOptions( const QString &profileName )
496 {
497   QgsSettings mySettings;
498   mySettings.remove( settingsKey( profileName ) );
499 }
500 
setCreateOptions()501 void QgsRasterFormatSaveOptionsWidget::setCreateOptions()
502 {
503   QgsSettings mySettings;
504   QStringList myProfiles;
505   QMap< QString, QString >::const_iterator i = mOptionsMap.constBegin();
506   while ( i != mOptionsMap.constEnd() )
507   {
508     setCreateOptions( i.key(), i.value() );
509     myProfiles << i.key();
510     ++i;
511   }
512   mySettings.setValue( mProvider + "/driverOptions/" + pseudoFormat().toLower() + "/profiles",
513                        myProfiles );
514   mySettings.setValue( mProvider + "/driverOptions/" + pseudoFormat().toLower() + "/defaultProfile",
515                        currentProfileKey().trimmed() );
516 }
517 
setCreateOptions(const QString & profileName,const QString & options)518 void QgsRasterFormatSaveOptionsWidget::setCreateOptions( const QString &profileName, const QString &options )
519 {
520   QgsSettings mySettings;
521   mySettings.setValue( settingsKey( profileName ), options.trimmed() );
522 }
523 
setCreateOptions(const QString & profileName,const QStringList & list)524 void QgsRasterFormatSaveOptionsWidget::setCreateOptions( const QString &profileName, const QStringList &list )
525 {
526   setCreateOptions( profileName, list.join( QLatin1Char( ' ' ) ) );
527 }
528 
profiles() const529 QStringList QgsRasterFormatSaveOptionsWidget::profiles() const
530 {
531   const QgsSettings mySettings;
532   return mySettings.value( mProvider + "/driverOptions/" + pseudoFormat().toLower() + "/profiles", "" ).toStringList();
533 }
534 
swapOptionsUI(int newIndex)535 void QgsRasterFormatSaveOptionsWidget::swapOptionsUI( int newIndex )
536 {
537   // set new page
538   int oldIndex;
539   if ( newIndex == -1 )
540   {
541     oldIndex = mOptionsStackedWidget->currentIndex();
542     newIndex = ( oldIndex + 1 ) % 2;
543   }
544   else
545   {
546     oldIndex = ( newIndex + 1 ) % 2;
547   }
548 
549   // resize pages to minimum - this works well with gdaltools merge ui, but not raster save as...
550   mOptionsStackedWidget->setCurrentIndex( newIndex );
551   mOptionsStackedWidget->widget( newIndex )->setSizePolicy(
552     QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred ) );
553   mOptionsStackedWidget->widget( oldIndex )->setSizePolicy(
554     QSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored ) );
555   layout()->activate();
556 
557   updateOptions();
558 }
559 
updateControls()560 void QgsRasterFormatSaveOptionsWidget::updateControls()
561 {
562   const bool valid = mProvider == QLatin1String( "gdal" ) && !mFormat.isEmpty();
563   mOptionsValidateButton->setEnabled( valid );
564   mOptionsHelpButton->setEnabled( valid );
565 }
566 
567 // map options label left mouse click to optionsToggle()
eventFilter(QObject * obj,QEvent * event)568 bool QgsRasterFormatSaveOptionsWidget::eventFilter( QObject *obj, QEvent *event )
569 {
570   if ( event->type() == QEvent::MouseButtonPress )
571   {
572     QMouseEvent *mouseEvent = static_cast<QMouseEvent *>( event );
573     if ( mouseEvent && ( mouseEvent->button() == Qt::RightButton ) )
574     {
575       QMenu *menu = nullptr;
576       QString text;
577       if ( mOptionsStackedWidget->currentIndex() == 0 )
578         text = tr( "Use simple interface" );
579       else
580         text = tr( "Use table interface" );
581       if ( obj->objectName() == QLatin1String( "mOptionsLineEdit" ) )
582       {
583         menu = mOptionsLineEdit->createStandardContextMenu();
584         menu->addSeparator();
585       }
586       else
587         menu = new QMenu( this );
588       QAction *action = new QAction( text, menu );
589       menu->addAction( action );
590       connect( action, &QAction::triggered, this, &QgsRasterFormatSaveOptionsWidget::swapOptionsUI );
591       menu->exec( mouseEvent->globalPos() );
592       delete menu;
593       return true;
594     }
595   }
596   // standard event processing
597   return QObject::eventFilter( obj, event );
598 }
599 
showEvent(QShowEvent * event)600 void QgsRasterFormatSaveOptionsWidget::showEvent( QShowEvent *event )
601 {
602   Q_UNUSED( event )
603   mOptionsTable->horizontalHeader()->resizeSection( 0, mOptionsTable->width() - 115 );
604   QgsDebugMsg( QStringLiteral( "done" ) );
605 }
606 
setOptions(const QString & options)607 void QgsRasterFormatSaveOptionsWidget::setOptions( const QString &options )
608 {
609   mBlockOptionUpdates++;
610   mOptionsTable->clearContents();
611 
612 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
613   const QStringList optionsList = options.trimmed().split( ' ', QString::SkipEmptyParts );
614 #else
615   const QStringList optionsList = options.trimmed().split( ' ', Qt::SkipEmptyParts );
616 #endif
617   for ( const QString &opt : optionsList )
618   {
619     const int rowCount = mOptionsTable->rowCount();
620     mOptionsTable->insertRow( rowCount );
621 
622     const QStringList values = opt.split( '=' );
623     if ( values.count() == 2 )
624     {
625       QTableWidgetItem *nameItem = new QTableWidgetItem( values.at( 0 ) );
626       mOptionsTable->setItem( rowCount, 0, nameItem );
627       QTableWidgetItem *valueItem = new QTableWidgetItem( values.at( 1 ) );
628       mOptionsTable->setItem( rowCount, 1, valueItem );
629     }
630   }
631 
632   // reset to no profile index, otherwise we are changing the definition of whichever profile
633   // is currently selected...
634   mProfileComboBox->setCurrentIndex( 0 );
635 
636   mOptionsMap[ currentProfileKey()] = options.trimmed();
637   mOptionsLineEdit->setText( options.trimmed() );
638   mOptionsLineEdit->setCursorPosition( 0 );
639 
640   mBlockOptionUpdates--;
641 }
642