QGIS API Documentation  2.0.1-Dufour
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsrasterformatsaveoptionswidget.cpp
Go to the documentation of this file.
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 
19 #include "qgslogger.h"
20 #include "qgsdialog.h"
21 #include "qgsrasterlayer.h"
22 #include "qgsproviderregistry.h"
23 
24 #include <QSettings>
25 #include <QInputDialog>
26 #include <QMessageBox>
27 #include <QTextEdit>
28 #include <QMouseEvent>
29 #include <QMenu>
30 
31 
32 QMap< QString, QStringList > QgsRasterFormatSaveOptionsWidget::mBuiltinProfiles;
33 
35  QgsRasterFormatSaveOptionsWidget::Type type, QString provider )
36  : QWidget( parent ), mFormat( format ), mProvider( provider ), mRasterLayer( 0 ),
37  mRasterFileName( QString() ), mPyramids( false ),
38  mPyramidsFormat( QgsRaster::PyramidsGTiff )
39 
40 {
41  setupUi( this );
42 
43  setType( type );
44 
45  if ( mBuiltinProfiles.isEmpty() )
46  {
47  // key=profileKey values=format,profileName,options
48  mBuiltinProfiles[ "z_adefault" ] = ( QStringList() << "" << tr( "Default" ) << "" );
49 
50  // these GTiff profiles are based on Tim's benchmarks at
51  // http://linfiniti.com/2011/05/gdal-efficiency-of-various-compression-algorithms/
52  // big: no compression | medium: reasonable size/speed tradeoff | small: smallest size
53  mBuiltinProfiles[ "z_gtiff_1big" ] =
54  ( QStringList() << "GTiff" << tr( "No compression" )
55  << "COMPRESS=NONE BIGTIFF=IF_NEEDED" );
56  mBuiltinProfiles[ "z_gtiff_2medium" ] =
57  ( QStringList() << "GTiff" << tr( "Low compression" )
58  << "COMPRESS=PACKBITS" );
59  mBuiltinProfiles[ "z_gtiff_3small" ] =
60  ( QStringList() << "GTiff" << tr( "High compression" )
61  << "COMPRESS=DEFLATE PREDICTOR=2 ZLEVEL=9" );
62  mBuiltinProfiles[ "z_gtiff_4jpeg" ] =
63  ( QStringList() << "GTiff" << tr( "JPEG compression" )
64  << "COMPRESS=JPEG JPEG_QUALITY=75" );
65 
66  // overview compression schemes for GTiff format, see
67  // http://www.gdal.org/gdaladdo.html and http://www.gdal.org/frmt_gtiff.html
68  // TODO - should we offer GDAL_TIFF_OVR_BLOCKSIZE option here or in QgsRasterPyramidsOptionsWidget ?
69  mBuiltinProfiles[ "z__pyramids_gtiff_1big" ] =
70  ( QStringList() << "_pyramids" << tr( "No compression" )
71  << "COMPRESS_OVERVIEW=NONE BIGTIFF_OVERVIEW=IF_NEEDED" );
72  mBuiltinProfiles[ "z__pyramids_gtiff_2medium" ] =
73  ( QStringList() << "_pyramids" << tr( "Low compression" )
74  << "COMPRESS_OVERVIEW=PACKBITS" );
75  mBuiltinProfiles[ "z__pyramids_gtiff_3small" ] =
76  ( QStringList() << "_pyramids" << tr( "High compression" )
77  << "COMPRESS_OVERVIEW=DEFLATE PREDICTOR_OVERVIEW=2 ZLEVEL=9" ); // how to set zlevel?
78  mBuiltinProfiles[ "z__pyramids_gtiff_4jpeg" ] =
79  ( QStringList() << "_pyramids" << tr( "JPEG compression" )
80  << "JPEG_QUALITY_OVERVIEW=75 COMPRESS_OVERVIEW=JPEG PHOTOMETRIC_OVERVIEW=YCBCR INTERLEAVE_OVERVIEW=PIXEL" );
81  }
82 
83  connect( mProfileComboBox, SIGNAL( currentIndexChanged( const QString & ) ),
84  this, SLOT( updateOptions() ) );
85  connect( mOptionsTable, SIGNAL( cellChanged( int, int ) ), this, SLOT( optionsTableChanged() ) );
86  connect( mOptionsHelpButton, SIGNAL( clicked() ), this, SLOT( helpOptions() ) );
87  connect( mOptionsValidateButton, SIGNAL( clicked() ), this, SLOT( validateOptions() ) );
88 
89  // create eventFilter to map right click to swapOptionsUI()
90  // mOptionsLabel->installEventFilter( this );
91  mOptionsLineEdit->installEventFilter( this );
92  mOptionsStackedWidget->installEventFilter( this );
93 
96 
97  QgsDebugMsg( "done" );
98 }
99 
101 {
102 }
103 
105 {
106  mFormat = format;
107  updateControls();
108  updateProfiles();
109 }
110 
112 {
113  mProvider = provider;
114  updateControls();
115 }
116 
117 // show/hide widgets - we need this function if widget is used in creator
119 {
120  QList< QWidget* > widgets = this->findChildren<QWidget *>();
121  if (( type == Table ) || ( type == LineEdit ) )
122  {
123  // hide all controls, except stacked widget
124  foreach ( QWidget* widget, widgets )
125  widget->setVisible( false );
126  mOptionsStackedWidget->setVisible( true );
127  foreach ( QWidget* widget, mOptionsStackedWidget->findChildren<QWidget *>() )
128  widget->setVisible( true );
129 
130  // show relevant page
131  if ( type == Table )
132  swapOptionsUI( 0 );
133  else if ( type == LineEdit )
134  swapOptionsUI( 1 );
135  }
136  else
137  {
138  // show all widgets, except profile buttons (unless Full)
139  foreach ( QWidget* widget, widgets )
140  widget->setVisible( true );
141  if ( type != Full )
142  mProfileButtons->setVisible( false );
143 
144  // show elevant page
145  if ( type == ProfileLineEdit )
146  swapOptionsUI( 1 );
147  }
148 }
149 
151 {
152  // build profiles list = user + builtin(last)
153  QString format = mPyramids ? "_pyramids" : mFormat;
154  QStringList profileKeys = profiles();
155  QMapIterator<QString, QStringList> it( mBuiltinProfiles );
156  while ( it.hasNext() )
157  {
158  it.next();
159  QString profileKey = it.key();
160  if ( ! profileKeys.contains( profileKey ) && it.value().count() > 0 )
161  {
162  // insert key if is for all formats or this format (GTiff)
163  if ( it.value()[0] == "" || it.value()[0] == format )
164  {
165  profileKeys.insert( 0, profileKey );
166  }
167  }
168  }
169  qSort( profileKeys );
170 
171  // populate mOptionsMap and mProfileComboBox
172  mOptionsMap.clear();
173  mProfileComboBox->blockSignals( true );
174  mProfileComboBox->clear();
175  foreach ( QString profileKey, profileKeys )
176  {
177  QString profileName, profileOptions;
178  profileOptions = createOptions( profileKey );
179  if ( mBuiltinProfiles.contains( profileKey ) )
180  {
181  profileName = mBuiltinProfiles[ profileKey ][ 1 ];
182  if ( profileOptions.isEmpty() )
183  profileOptions = mBuiltinProfiles[ profileKey ][ 2 ];
184  }
185  else
186  {
187  profileName = profileKey;
188  }
189  mOptionsMap[ profileKey ] = profileOptions;
190  mProfileComboBox->addItem( profileName, profileKey );
191  }
192 
193  // update UI
194  mProfileComboBox->blockSignals( false );
195  // mProfileComboBox->setCurrentIndex( 0 );
196  QSettings mySettings;
197  mProfileComboBox->setCurrentIndex( mProfileComboBox->findData( mySettings.value(
198  mProvider + "/driverOptions/" + format.toLower() + "/defaultProfile",
199  "z_adefault" ) ) );
200  updateOptions();
201 }
202 
204 {
205  QString myOptions = mOptionsMap.value( currentProfileKey() );
206  QStringList myOptionsList = myOptions.trimmed().split( " ", QString::SkipEmptyParts );
207 
208  if ( mOptionsStackedWidget->currentIndex() == 0 )
209  {
210  mOptionsTable->setRowCount( 0 );
211  for ( int i = 0; i < myOptionsList.count(); i++ )
212  {
213  QStringList key_value = myOptionsList[i].split( "=" );
214  if ( key_value.count() == 2 )
215  {
216  mOptionsTable->insertRow( i );
217  mOptionsTable->setItem( i, 0, new QTableWidgetItem( key_value[0] ) );
218  mOptionsTable->setItem( i, 1, new QTableWidgetItem( key_value[1] ) );
219  }
220  }
221  }
222  else
223  {
224  mOptionsLineEdit->setText( myOptions );
225  mOptionsLineEdit->setCursorPosition( 0 );
226  }
227 
228  emit optionsChanged();
229 }
230 
232 {
234 }
235 
236 // typedefs for gdal provider function pointers
237 typedef QString validateCreationOptionsFormat_t( const QStringList& createOptions, QString format );
238 typedef QString helpCreationOptionsFormat_t( QString format );
239 
241 {
242  QString message;
243 
244  if ( mProvider == "gdal" && mFormat != "" && ! mPyramids )
245  {
246  // get helpCreationOptionsFormat() function ptr for provider
247  QLibrary *library = QgsProviderRegistry::instance()->providerLibrary( mProvider );
248  if ( library )
249  {
250  helpCreationOptionsFormat_t * helpCreationOptionsFormat =
251  ( helpCreationOptionsFormat_t * ) cast_to_fptr( library->resolve( "helpCreationOptionsFormat" ) );
252  if ( helpCreationOptionsFormat )
253  {
254  message = helpCreationOptionsFormat( mFormat );
255  }
256  else
257  {
258  message = library->fileName() + " does not have helpCreationOptionsFormat";
259  }
260  }
261  else
262  message = QString( "cannot load provider library %1" ).arg( mProvider );
263 
264 
265  if ( message.isEmpty() )
266  message = tr( "Cannot get create options for driver %1" ).arg( mFormat );
267  }
268  else if ( mProvider == "gdal" && mPyramids )
269  {
270  message = tr( "For details on pyramids options please see the following pages" );
271  message += "\n\nhttp://www.gdal.org/gdaladdo.html\n\nhttp://www.gdal.org/frmt_gtiff.html";
272  }
273  else
274  message = tr( "No help available" );
275 
276  // show simple non-modal dialog - should we make the basic xml prettier?
277  QgsDialog *dlg = new QgsDialog( this );
278  QTextEdit *textEdit = new QTextEdit( dlg );
279  textEdit->setReadOnly( true );
280  // message = tr( "Create Options:\n\n%1" ).arg( message );
281  textEdit->setText( message );
282  dlg->layout()->addWidget( textEdit );
283  dlg->resize( 600, 400 );
284 #ifdef Q_WS_MAC
285  dlg->exec(); //modal
286 #else
287  dlg->show(); //non modal
288 #endif
289 }
290 
291 QString QgsRasterFormatSaveOptionsWidget::validateOptions( bool gui, bool reportOK )
292 {
293  QStringList createOptions = options();
294  QString message;
295 
296  QgsDebugMsg( QString( "layer: [%1] file: [%2] format: [%3]" ).arg( mRasterLayer ? mRasterLayer->id() : "none" ).arg( mRasterFileName ).arg( mFormat ) );
297  // if no rasterLayer is defined, but we have a raster fileName, then create a temp. rasterLayer to validate options
298  // ideally we should keep it for future access, but this is trickier
299  QgsRasterLayer* rasterLayer = mRasterLayer;
300  bool tmpLayer = false;
301  if ( !( mRasterLayer && rasterLayer->dataProvider() ) && ! mRasterFileName.isNull() )
302  {
303  // temporarily override /Projections/defaultBehaviour to avoid dialog prompt
304  // this is taken from qgsbrowserdockwidget.cpp
305  // TODO - integrate this into qgis core
306  QSettings settings;
307  QString defaultProjectionOption = settings.value( "/Projections/defaultBehaviour", "prompt" ).toString();
308  if ( settings.value( "/Projections/defaultBehaviour", "prompt" ).toString() == "prompt" )
309  {
310  settings.setValue( "/Projections/defaultBehaviour", "useProject" );
311  }
312  tmpLayer = true;
313  rasterLayer = new QgsRasterLayer( mRasterFileName, QFileInfo( mRasterFileName ).baseName(), "gdal" );
314  // restore /Projections/defaultBehaviour
315  if ( defaultProjectionOption == "prompt" )
316  {
317  settings.setValue( "/Projections/defaultBehaviour", defaultProjectionOption );
318  }
319  }
320 
321  if ( mProvider == "gdal" && mPyramids )
322  {
323  if ( rasterLayer && rasterLayer->dataProvider() )
324  {
325  QgsDebugMsg( "calling validate pyramids on layer's data provider" );
326  message = rasterLayer->dataProvider()->validatePyramidsConfigOptions( mPyramidsFormat, createOptions, mFormat );
327  }
328  else
329  {
330  message = tr( "cannot validate pyramid options" );
331  }
332  }
333  else if ( !createOptions.isEmpty() && mProvider == "gdal" && mFormat != "" )
334  {
335  if ( rasterLayer && rasterLayer->dataProvider() )
336  {
337  QgsDebugMsg( "calling validate on layer's data provider" );
338  message = rasterLayer->dataProvider()->validateCreationOptions( createOptions, mFormat );
339  }
340  else
341  {
342  // get validateCreationOptionsFormat() function ptr for provider
343  QLibrary *library = QgsProviderRegistry::instance()->providerLibrary( mProvider );
344  if ( library )
345  {
346  validateCreationOptionsFormat_t * validateCreationOptionsFormat =
347  ( validateCreationOptionsFormat_t * ) cast_to_fptr( library->resolve( "validateCreationOptionsFormat" ) );
348  if ( validateCreationOptionsFormat )
349  {
350  message = validateCreationOptionsFormat( createOptions, mFormat );
351  }
352  else
353  {
354  message = library->fileName() + " does not have validateCreationOptionsFormat";
355  }
356  }
357  else
358  message = QString( "cannot load provider library %1" ).arg( mProvider );
359  }
360  }
361  else if ( ! createOptions.isEmpty() )
362  {
363  QMessageBox::information( this, "", tr( "Cannot validate creation options" ), QMessageBox::Close );
364  if ( tmpLayer )
365  delete rasterLayer;
366  return QString();
367  }
368 
369  if ( gui )
370  {
371  if ( message.isNull() )
372  {
373  if ( reportOK )
374  QMessageBox::information( this, "", tr( "Valid" ), QMessageBox::Close );
375  }
376  else
377  {
378  QMessageBox::warning( this, "", 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" ) ).arg( message ), QMessageBox::Close );
379  }
380  }
381 
382  if ( tmpLayer )
383  delete rasterLayer;
384 
385  return message;
386 }
387 
389 {
390  QTableWidgetItem *key, *value;
391  QString options;
392  for ( int i = 0 ; i < mOptionsTable->rowCount(); i++ )
393  {
394  key = mOptionsTable->item( i, 0 );
395  if ( ! key || key->text().isEmpty() )
396  continue;
397  value = mOptionsTable->item( i, 1 );
398  if ( ! value || value->text().isEmpty() )
399  continue;
400  options += key->text() + "=" + value->text() + " ";
401  }
402  options = options.trimmed();
404  mOptionsLineEdit->setText( options );
405  mOptionsLineEdit->setCursorPosition( 0 );
406 }
407 
409 {
410  mOptionsMap[ currentProfileKey()] = mOptionsLineEdit->text().trimmed();
411 }
412 
414 {
415  QString profileName = QInputDialog::getText( this, "", tr( "Profile name:" ) );
416  if ( ! profileName.isEmpty() )
417  {
418  profileName = profileName.trimmed();
419  mOptionsMap[ profileName ] = "";
420  mProfileComboBox->addItem( profileName, profileName );
421  mProfileComboBox->setCurrentIndex( mProfileComboBox->count() - 1 );
422  }
423 }
424 
426 {
427  int index = mProfileComboBox->currentIndex();
428  QString profileKey = currentProfileKey();
429  if ( index != -1 && ! mBuiltinProfiles.contains( profileKey ) )
430  {
431  mOptionsMap.remove( profileKey );
432  mProfileComboBox->removeItem( index );
433  }
434 }
435 
437 {
438  QString profileKey = currentProfileKey();
439  if ( mBuiltinProfiles.contains( profileKey ) )
440  {
441  mOptionsMap[ profileKey ] = mBuiltinProfiles[ profileKey ][ 2 ];
442  }
443  else
444  {
445  mOptionsMap[ profileKey ] = "";
446  }
447  mOptionsLineEdit->setText( mOptionsMap.value( currentProfileKey() ) );
448  mOptionsLineEdit->setCursorPosition( 0 );
449  updateOptions();
450 }
451 
453 {
454  mOptionsDeleteButton->setEnabled( mOptionsTable->currentRow() >= 0 );
455 }
456 
458 {
459  mOptionsTable->insertRow( mOptionsTable->rowCount() );
460  // select the added row
461  int newRow = mOptionsTable->rowCount() - 1;
462  QTableWidgetItem* item = new QTableWidgetItem();
463  mOptionsTable->setItem( newRow, 0, item );
464  mOptionsTable->setCurrentItem( item );
465 }
466 
468 {
469  if ( mOptionsTable->currentRow() >= 0 )
470  {
471  mOptionsTable->removeRow( mOptionsTable->currentRow() );
472  // select the previous row or the next one if there is no previous row
473  QTableWidgetItem* item = mOptionsTable->item( mOptionsTable->currentRow(), 0 );
474  mOptionsTable->setCurrentItem( item );
476  }
477 }
478 
479 
480 QString QgsRasterFormatSaveOptionsWidget::settingsKey( QString profileName ) const
481 {
482  if ( profileName != "" )
483  profileName = "/profile_" + profileName;
484  else
485  profileName = "/profile_default" + profileName;
486  return mProvider + "/driverOptions/" + mFormat.toLower() + profileName + "/create";
487 }
488 
490 {
491  return mProfileComboBox->itemData( mProfileComboBox->currentIndex() ).toString();
492 }
493 
495 {
496  return mOptionsMap.value( currentProfileKey() ).trimmed().split( " ", QString::SkipEmptyParts );
497 }
498 
499 QString QgsRasterFormatSaveOptionsWidget::createOptions( QString profileName ) const
500 {
501  QSettings mySettings;
502  return mySettings.value( settingsKey( profileName ), "" ).toString();
503 }
504 
506 {
507  QSettings mySettings;
508  mySettings.remove( settingsKey( profileName ) );
509 }
510 
512 {
513  QSettings mySettings;
514  QString myProfiles;
515  QMap< QString, QString >::iterator i = mOptionsMap.begin();
516  while ( i != mOptionsMap.end() )
517  {
518  setCreateOptions( i.key(), i.value() );
519  myProfiles += i.key() + QString( " " );
520  ++i;
521  }
522  mySettings.setValue( mProvider + "/driverOptions/" + mFormat.toLower() + "/profiles",
523  myProfiles.trimmed() );
524  mySettings.setValue( mProvider + "/driverOptions/" + mFormat.toLower() + "/defaultProfile",
525  currentProfileKey().trimmed() );
526 }
527 
528 void QgsRasterFormatSaveOptionsWidget::setCreateOptions( QString profileName, QString options )
529 {
530  QSettings mySettings;
531  mySettings.setValue( settingsKey( profileName ), options.trimmed() );
532 }
533 
534 void QgsRasterFormatSaveOptionsWidget::setCreateOptions( QString profileName, QStringList list )
535 {
536  setCreateOptions( profileName, list.join( " " ) );
537 }
538 
540 {
541  QSettings mySettings;
542  return mySettings.value( mProvider + "/driverOptions/" + mFormat.toLower() + "/profiles", "" ).toString().trimmed().split( " ", QString::SkipEmptyParts );
543 }
544 
546 {
547  // set new page
548  int oldIndex;
549  if ( newIndex == -1 )
550  {
551  oldIndex = mOptionsStackedWidget->currentIndex();
552  newIndex = ( oldIndex + 1 ) % 2;
553  }
554  else
555  {
556  oldIndex = ( newIndex + 1 ) % 2;
557  }
558 
559  // resize pages to minimum - this works well with gdaltools merge ui, but not raster save as...
560  mOptionsStackedWidget->setCurrentIndex( newIndex );
561  mOptionsStackedWidget->widget( newIndex )->setSizePolicy(
562  QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred ) );
563  mOptionsStackedWidget->widget( oldIndex )->setSizePolicy(
564  QSizePolicy( QSizePolicy::Ignored, QSizePolicy::Ignored ) );
565  layout()->activate();
566 
567  updateOptions();
568 }
569 
571 {
572  bool valid = mProvider == "gdal" && mFormat != "";
573  mOptionsValidateButton->setEnabled( valid );
574  mOptionsHelpButton->setEnabled( valid );
575 }
576 
577 // map options label left mouse click to optionsToggle()
578 bool QgsRasterFormatSaveOptionsWidget::eventFilter( QObject *obj, QEvent *event )
579 {
580  if ( event->type() == QEvent::MouseButtonPress )
581  {
582  QMouseEvent *mouseEvent = static_cast<QMouseEvent *>( event );
583  if ( mouseEvent && ( mouseEvent->button() == Qt::RightButton ) )
584  {
585  QMenu* menu = 0;
586  QString text;
587  if ( mOptionsStackedWidget->currentIndex() == 0 )
588  text = tr( "Use simple interface" );
589  else
590  text = tr( "Use table interface" );
591  if ( obj->objectName() == "mOptionsLineEdit" )
592  {
593  menu = mOptionsLineEdit->createStandardContextMenu();
594  menu->addSeparator();
595  }
596  else
597  menu = new QMenu( this );
598  QAction* action = new QAction( text, menu );
599  menu->addAction( action );
600  connect( action, SIGNAL( triggered() ), this, SLOT( swapOptionsUI() ) );
601  menu->exec( mouseEvent->globalPos() );
602  delete menu;
603  return true;
604  }
605  }
606  // standard event processing
607  return QObject::eventFilter( obj, event );
608 }
609 
611 {
612  Q_UNUSED( event );
613  mOptionsTable->horizontalHeader()->resizeSection( 0, mOptionsTable->width() - 115 );
614  QgsDebugMsg( "done" );
615 }
616