QGIS API Documentation  2.99.0-Master (a18066b)
qgssinglebandpseudocolorrendererwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssinglebandpseudocolorrendererwidget.cpp
3  ------------------------------------------
4  begin : February 2012
5  copyright : (C) 2012 by Marco Hugentobler
6  email : marco at sourcepole dot ch
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 
20 #include "qgsrasterlayer.h"
21 #include "qgsrasterdataprovider.h"
22 #include "qgsrastershader.h"
23 #include "qgsrasterminmaxwidget.h"
24 #include "qgstreewidgetitem.h"
25 
26 // for color ramps - todo add rasterStyle and refactor raster vs. vector ramps
27 #include "qgsstyle.h"
28 #include "qgscolorramp.h"
29 #include "qgscolorrampbutton.h"
30 #include "qgscolordialog.h"
31 
32 #include <QCursor>
33 #include <QPushButton>
34 #include <QInputDialog>
35 #include <QFileDialog>
36 #include <QMenu>
37 #include <QMessageBox>
38 #include <QSettings>
39 #include <QTextStream>
40 #include <QTreeView>
41 
43  : QgsRasterRendererWidget( layer, extent )
44  , mMinMaxWidget( nullptr )
45  , mDisableMinMaxWidgetRefresh( false )
46  , mMinMaxOrigin( 0 )
47 {
48  QSettings settings;
49 
50  setupUi( this );
51 
52  contextMenu = new QMenu( tr( "Options" ), this );
53  contextMenu->addAction( tr( "Change color" ), this, SLOT( changeColor() ) );
54  contextMenu->addAction( tr( "Change transparency" ), this, SLOT( changeTransparency() ) );
55 
56  mColormapTreeWidget->setColumnWidth( ColorColumn, 50 );
57  mColormapTreeWidget->setContextMenuPolicy( Qt::CustomContextMenu );
58  mColormapTreeWidget->setSelectionMode( QAbstractItemView::ExtendedSelection );
59  connect( mColormapTreeWidget, &QTreeView::customContextMenuRequested, [=]( const QPoint& ) { contextMenu->exec( QCursor::pos() ); }
60  );
61 
62  QString defaultPalette = settings.value( QStringLiteral( "/Raster/defaultPalette" ), "" ).toString();
63  btnColorRamp->setColorRampFromName( defaultPalette );
64 
65  if ( !mRasterLayer )
66  {
67  return;
68  }
69 
71  if ( !provider )
72  {
73  return;
74  }
75 
76  // Must be before adding items to mBandComboBox (signal)
77  mMinLineEdit->setValidator( new QDoubleValidator( mMinLineEdit ) );
78  mMaxLineEdit->setValidator( new QDoubleValidator( mMaxLineEdit ) );
79 
80  mMinMaxWidget = new QgsRasterMinMaxWidget( layer, this );
81  mMinMaxWidget->setExtent( extent );
82  mMinMaxWidget->setMapCanvas( mCanvas );
83 
84  QHBoxLayout *layout = new QHBoxLayout();
85  layout->setContentsMargins( 0, 0, 0, 0 );
86  mMinMaxContainerWidget->setLayout( layout );
87  layout->addWidget( mMinMaxWidget );
88 
91 
92  //fill available bands into combo box
93  int nBands = provider->bandCount();
94  for ( int i = 1; i <= nBands; ++i ) //band numbering seem to start at 1
95  {
96  mBandComboBox->addItem( displayBandName( i ), i );
97  }
98 
99  mColorInterpolationComboBox->addItem( tr( "Discrete" ), QgsColorRampShader::Discrete );
100  mColorInterpolationComboBox->addItem( tr( "Linear" ), QgsColorRampShader::Interpolated );
101  mColorInterpolationComboBox->addItem( tr( "Exact" ), QgsColorRampShader::Exact );
102  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
103 
104  mClassificationModeComboBox->addItem( tr( "Continuous" ), QgsColorRampShader::Continuous );
105  mClassificationModeComboBox->addItem( tr( "Equal interval" ), QgsColorRampShader::EqualInterval );
106  mClassificationModeComboBox->addItem( tr( "Quantile" ), QgsColorRampShader::Quantile );
107  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::Continuous ) );
108 
109  mNumberOfEntriesSpinBox->setValue( 5 ); // some default
110 
111  setFromRenderer( layer->renderer() );
112 
113  // If there is currently no min/max, load default with user current default options
114  if ( mMinLineEdit->text().isEmpty() || mMaxLineEdit->text().isEmpty() )
115  {
116  QgsRasterMinMaxOrigin minMaxOrigin = mMinMaxWidget->minMaxOrigin();
117  if ( minMaxOrigin.limits() == QgsRasterMinMaxOrigin::None )
118  {
120  mMinMaxWidget->setFromMinMaxOrigin( minMaxOrigin );
121  }
122  mMinMaxWidget->doComputations();
123  }
124 
125  on_mClassificationModeComboBox_currentIndexChanged( 0 );
126 
127  resetClassifyButton();
128 
129  connect( mClassificationModeComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( classify() ) );
130  connect( mClassifyButton, &QPushButton::clicked, this, &QgsSingleBandPseudoColorRendererWidget::applyColorRamp );
131  connect( btnColorRamp, &QgsColorRampButton::colorRampChanged, this, &QgsSingleBandPseudoColorRendererWidget::applyColorRamp );
132  connect( mNumberOfEntriesSpinBox, SIGNAL( valueChanged( int ) ), this, SLOT( classify() ) );
133  connect( mBandComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( classify() ) );
134  connect( mClipCheckBox, SIGNAL( toggled( bool ) ), this, SIGNAL( widgetChanged() ) );
135 }
136 
138 {
139  QgsRasterShader* rasterShader = new QgsRasterShader();
140  QgsColorRampShader* colorRampShader = new QgsColorRampShader( lineEditValue( mMinLineEdit ), lineEditValue( mMaxLineEdit ) );
141  colorRampShader->setColorRampType( static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ) );
142  colorRampShader->setClassificationMode( static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ) );
143  colorRampShader->setClip( mClipCheckBox->isChecked() );
144 
145  //iterate through mColormapTreeWidget and set colormap info of layer
146  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
147  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
148  QTreeWidgetItem* currentItem = nullptr;
149  for ( int i = 0; i < topLevelItemCount; ++i )
150  {
151  currentItem = mColormapTreeWidget->topLevelItem( i );
152  if ( !currentItem )
153  {
154  continue;
155  }
156  QgsColorRampShader::ColorRampItem newColorRampItem;
157  newColorRampItem.value = currentItem->text( ValueColumn ).toDouble();
158  newColorRampItem.color = currentItem->background( ColorColumn ).color();
159  newColorRampItem.label = currentItem->text( LabelColumn );
160  colorRampItems.append( newColorRampItem );
161  }
162  // sort the shader items
163  std::sort( colorRampItems.begin(), colorRampItems.end() );
164  colorRampShader->setColorRampItemList( colorRampItems );
165 
166  if ( !btnColorRamp->isNull() )
167  {
168  colorRampShader->setSourceColorRamp( btnColorRamp->colorRamp() );
169  }
170 
171  rasterShader->setRasterShaderFunction( colorRampShader );
172 
173  int bandNumber = mBandComboBox->currentData().toInt();
175  renderer->setClassificationMin( lineEditValue( mMinLineEdit ) );
176  renderer->setClassificationMax( lineEditValue( mMaxLineEdit ) );
177  renderer->setMinMaxOrigin( mMinMaxWidget->minMaxOrigin() );
178  return renderer;
179 }
180 
182 {
183  mMinMaxWidget->doComputations();
184  if ( mColormapTreeWidget->topLevelItemCount() == 0 )
185  applyColorRamp();
186 }
187 
189 {
191  mMinMaxWidget->setMapCanvas( canvas );
192 }
193 
198 void QgsSingleBandPseudoColorRendererWidget::autoLabel()
199 {
200  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
201  bool discrete = interpolation == QgsColorRampShader::Discrete;
202  QString unit = mUnitLineEdit->text();
203  QString label;
204  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
205  QTreeWidgetItem* currentItem = nullptr;
206  for ( int i = 0; i < topLevelItemCount; ++i )
207  {
208  currentItem = mColormapTreeWidget->topLevelItem( i );
209  //If the item is null or does not have a pixel values set, skip
210  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
211  {
212  continue;
213  }
214 
215  if ( discrete )
216  {
217  if ( i == 0 )
218  {
219  label = "<= " + currentItem->text( ValueColumn ) + unit;
220  }
221  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
222  {
223  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + unit;
224  }
225  else
226  {
227  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn ) + unit;
228  }
229  }
230  else
231  {
232  label = currentItem->text( ValueColumn ) + unit;
233  }
234 
235  if ( currentItem->text( LabelColumn ).isEmpty() || currentItem->text( LabelColumn ) == label || currentItem->foreground( LabelColumn ).color() == QColor( Qt::gray ) )
236  {
237  currentItem->setText( LabelColumn, label );
238  currentItem->setForeground( LabelColumn, QBrush( QColor( Qt::gray ) ) );
239  }
240  }
241 }
242 
244 void QgsSingleBandPseudoColorRendererWidget::setUnitFromLabels()
245 {
246  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
247  bool discrete = interpolation == QgsColorRampShader::Discrete;
248  QStringList allSuffixes;
249  QString label;
250  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
251  QTreeWidgetItem* currentItem = nullptr;
252  for ( int i = 0; i < topLevelItemCount; ++i )
253  {
254  currentItem = mColormapTreeWidget->topLevelItem( i );
255  //If the item is null or does not have a pixel values set, skip
256  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
257  {
258  continue;
259  }
260 
261  if ( discrete )
262  {
263  if ( i == 0 )
264  {
265  label = "<= " + currentItem->text( ValueColumn );
266  }
267  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
268  {
269  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn );
270  }
271  else
272  {
273  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn );
274  }
275  }
276  else
277  {
278  label = currentItem->text( ValueColumn );
279  }
280 
281  if ( currentItem->text( LabelColumn ).startsWith( label ) )
282  {
283  allSuffixes.append( currentItem->text( LabelColumn ).mid( label.length() ) );
284  }
285  }
286  // find most common suffix
287  QStringList suffixes = QStringList( allSuffixes );
288  suffixes.removeDuplicates();
289  int max = 0;
290  QString unit;
291  for ( int i = 0; i < suffixes.count(); ++i )
292  {
293  int n = allSuffixes.count( suffixes[i] );
294  if ( n > max )
295  {
296  max = n;
297  unit = suffixes[i];
298  }
299  }
300  // Set this suffix as unit if at least used twice
301  if ( max >= 2 )
302  {
303  mUnitLineEdit->setText( unit );
304  }
305  autoLabel();
306 }
307 
308 void QgsSingleBandPseudoColorRendererWidget::on_mAddEntryButton_clicked()
309 {
310  QgsTreeWidgetItemObject* newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
311  newItem->setText( ValueColumn, QStringLiteral( "0" ) );
312  newItem->setBackground( ColorColumn, QBrush( QColor( Qt::magenta ) ) );
313  newItem->setText( LabelColumn, QString() );
314  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
315  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
316  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
317  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
318  autoLabel();
319  emit widgetChanged();
320 }
321 
322 void QgsSingleBandPseudoColorRendererWidget::on_mDeleteEntryButton_clicked()
323 {
324  QList<QTreeWidgetItem *> itemList;
325  itemList = mColormapTreeWidget->selectedItems();
326  if ( itemList.isEmpty() )
327  {
328  return;
329  }
330 
331  Q_FOREACH ( QTreeWidgetItem *item, itemList )
332  {
333  delete item;
334  }
335  emit widgetChanged();
336 }
337 
339 {
340  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
341  if ( !ramp )
342  {
343  return;
344  }
345 
346  QgsSingleBandPseudoColorRenderer *pr = new QgsSingleBandPseudoColorRenderer( mRasterLayer->dataProvider(), mBandComboBox->currentData().toInt(), nullptr );
347  pr->setClassificationMin( lineEditValue( mMinLineEdit ) );
348  pr->setClassificationMax( lineEditValue( mMaxLineEdit ) );
349  pr->createShader( ramp.get(), static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ), static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ), mNumberOfEntriesSpinBox->value(), mClipCheckBox->isChecked(), minMaxWidget()->extent() );
350 
351  const QgsRasterShader* rasterShader = pr->shader();
352  if ( rasterShader )
353  {
354  const QgsColorRampShader* colorRampShader = dynamic_cast<const QgsColorRampShader*>( rasterShader->rasterShaderFunction() );
355  if ( colorRampShader )
356  {
357  mColormapTreeWidget->clear();
358 
359  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
360  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
361  for ( ; it != colorRampItemList.end(); ++it )
362  {
363  QgsTreeWidgetItemObject* newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
364  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
365  newItem->setBackground( ColorColumn, QBrush( it->color ) );
366  newItem->setText( LabelColumn, it->label );
367  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
368  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
369  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
370  }
371  mClipCheckBox->setChecked( colorRampShader->clip() );
372  }
373  }
374 
375  autoLabel();
376  emit widgetChanged();
377 }
378 
379 void QgsSingleBandPseudoColorRendererWidget::on_mClassificationModeComboBox_currentIndexChanged( int index )
380 {
381  QgsColorRampShader::ClassificationMode mode = static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->itemData( index ).toInt() );
382  mNumberOfEntriesSpinBox->setEnabled( mode != QgsColorRampShader::Continuous );
383  mMinLineEdit->setEnabled( mode != QgsColorRampShader::Quantile );
384  mMaxLineEdit->setEnabled( mode != QgsColorRampShader::Quantile );
385 }
386 
387 void QgsSingleBandPseudoColorRendererWidget::applyColorRamp()
388 {
389  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
390  if ( !ramp )
391  {
392  return;
393  }
394 
395  if ( !btnColorRamp->colorRampName().isEmpty() )
396  {
397  // Remember last used color ramp
398  QSettings settings;
399  settings.setValue( QStringLiteral( "/Raster/defaultPalette" ), btnColorRamp->colorRampName() );
400  }
401 
402  bool enableContinuous = ( ramp->count() > 0 );
403  mClassificationModeComboBox->setEnabled( enableContinuous );
404  if ( !enableContinuous )
405  {
406  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::EqualInterval ) );
407  }
408 
409  classify();
410 }
411 
412 void QgsSingleBandPseudoColorRendererWidget::populateColormapTreeWidget( const QList<QgsColorRampShader::ColorRampItem>& colorRampItems )
413 {
414  mColormapTreeWidget->clear();
415  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItems.constBegin();
416  for ( ; it != colorRampItems.constEnd(); ++it )
417  {
418  QgsTreeWidgetItemObject* newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
419  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
420  newItem->setBackground( ColorColumn, QBrush( it->color ) );
421  newItem->setText( LabelColumn, it->label );
422  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
423  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
424  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
425  }
426  setUnitFromLabels();
427 }
428 
429 void QgsSingleBandPseudoColorRendererWidget::on_mLoadFromBandButton_clicked()
430 {
431  if ( !mRasterLayer || !mRasterLayer->dataProvider() )
432  {
433  return;
434  }
435 
436  int bandIndex = mBandComboBox->currentData().toInt();
437 
438 
439  QList<QgsColorRampShader::ColorRampItem> colorRampList = mRasterLayer->dataProvider()->colorTable( bandIndex );
440  if ( !colorRampList.isEmpty() )
441  {
442  populateColormapTreeWidget( colorRampList );
443  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
444  }
445  else
446  {
447  QMessageBox::warning( this, tr( "Load Color Map" ), tr( "The color map for band %1 has no entries" ).arg( bandIndex ) );
448  }
449  emit widgetChanged();
450 }
451 
452 void QgsSingleBandPseudoColorRendererWidget::on_mLoadFromFileButton_clicked()
453 {
454  int lineCounter = 0;
455  bool importError = false;
456  QString badLines;
457  QSettings settings;
458  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
459  QString fileName = QFileDialog::getOpenFileName( this, tr( "Open file" ), lastDir, tr( "Textfile (*.txt)" ) );
460  QFile inputFile( fileName );
461  if ( inputFile.open( QFile::ReadOnly ) )
462  {
463  //clear the current tree
464  mColormapTreeWidget->clear();
465 
466  QTextStream inputStream( &inputFile );
467  QString inputLine;
468  QStringList inputStringComponents;
469  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
470 
471  //read through the input looking for valid data
472  while ( !inputStream.atEnd() )
473  {
474  lineCounter++;
475  inputLine = inputStream.readLine();
476  if ( !inputLine.isEmpty() )
477  {
478  if ( !inputLine.simplified().startsWith( '#' ) )
479  {
480  if ( inputLine.contains( QLatin1String( "INTERPOLATION" ), Qt::CaseInsensitive ) )
481  {
482  inputStringComponents = inputLine.split( ':' );
483  if ( inputStringComponents.size() == 2 )
484  {
485  if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "INTERPOLATED" ), Qt::CaseInsensitive ) == 0 )
486  {
487  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
488  }
489  else if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "DISCRETE" ), Qt::CaseInsensitive ) == 0 )
490  {
491  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Discrete ) );
492  }
493  else
494  {
495  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Exact ) );
496  }
497  }
498  else
499  {
500  importError = true;
501  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
502  }
503  }
504  else
505  {
506  inputStringComponents = inputLine.split( ',' );
507  if ( inputStringComponents.size() == 6 )
508  {
509  QgsColorRampShader::ColorRampItem currentItem( inputStringComponents[0].toDouble(),
510  QColor::fromRgb( inputStringComponents[1].toInt(), inputStringComponents[2].toInt(),
511  inputStringComponents[3].toInt(), inputStringComponents[4].toInt() ),
512  inputStringComponents[5] );
513  colorRampItems.push_back( currentItem );
514  }
515  else
516  {
517  importError = true;
518  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
519  }
520  }
521  }
522  }
523  lineCounter++;
524  }
525  populateColormapTreeWidget( colorRampItems );
526 
527  QFileInfo fileInfo( fileName );
528  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
529 
530  if ( importError )
531  {
532  QMessageBox::warning( this, tr( "Import Error" ), tr( "The following lines contained errors\n\n" ) + badLines );
533  }
534  }
535  else if ( !fileName.isEmpty() )
536  {
537  QMessageBox::warning( this, tr( "Read access denied" ), tr( "Read access denied. Adjust the file permissions and try again.\n\n" ) );
538  }
539  emit widgetChanged();
540 }
541 
542 void QgsSingleBandPseudoColorRendererWidget::on_mExportToFileButton_clicked()
543 {
544  QSettings settings;
545  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
546  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save file" ), lastDir, tr( "Textfile (*.txt)" ) );
547  if ( !fileName.isEmpty() )
548  {
549  if ( !fileName.endsWith( QLatin1String( ".txt" ), Qt::CaseInsensitive ) )
550  {
551  fileName = fileName + ".txt";
552  }
553 
554  QFile outputFile( fileName );
555  if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
556  {
557  QTextStream outputStream( &outputFile );
558  outputStream << "# " << tr( "QGIS Generated Color Map Export File" ) << '\n';
559  outputStream << "INTERPOLATION:";
560  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
561  switch ( interpolation )
562  {
564  outputStream << "INTERPOLATED\n";
565  break;
567  outputStream << "DISCRETE\n";
568  break;
570  outputStream << "EXACT\n";
571  break;
572  }
573 
574  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
575  QTreeWidgetItem* currentItem = nullptr;
576  QColor color;
577  for ( int i = 0; i < topLevelItemCount; ++i )
578  {
579  currentItem = mColormapTreeWidget->topLevelItem( i );
580  if ( !currentItem )
581  {
582  continue;
583  }
584  color = currentItem->background( ColorColumn ).color();
585  outputStream << currentItem->text( ValueColumn ).toDouble() << ',';
586  outputStream << color.red() << ',' << color.green() << ',' << color.blue() << ',' << color.alpha() << ',';
587  if ( currentItem->text( LabelColumn ).isEmpty() )
588  {
589  outputStream << "Color entry " << i + 1 << '\n';
590  }
591  else
592  {
593  outputStream << currentItem->text( LabelColumn ) << '\n';
594  }
595  }
596  outputStream.flush();
597  outputFile.close();
598 
599  QFileInfo fileInfo( fileName );
600  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
601  }
602  else
603  {
604  QMessageBox::warning( this, tr( "Write access denied" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
605  }
606  }
607 }
608 
609 void QgsSingleBandPseudoColorRendererWidget::on_mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem* item, int column )
610 {
611  if ( !item )
612  {
613  return;
614  }
615 
616  if ( column == ColorColumn )
617  {
618  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
619  QColor newColor = QgsColorDialog::getColor( item->background( column ).color(), this, QStringLiteral( "Change color" ), true );
620  if ( newColor.isValid() )
621  {
622  item->setBackground( ColorColumn, QBrush( newColor ) );
623  emit widgetChanged();
624  }
625  }
626  else
627  {
628  if ( column == LabelColumn )
629  {
630  // Set text color to default black, which signifies a manually edited label
631  item->setForeground( LabelColumn, QBrush() );
632  }
633  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
634  }
635 }
636 
638 void QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited( QTreeWidgetItem* item, int column )
639 {
640  Q_UNUSED( item );
641 
642  if ( column == ValueColumn )
643  {
644  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
645  autoLabel();
646  emit widgetChanged();
647  }
648  else if ( column == LabelColumn )
649  {
650  // call autoLabel to fill when empty or gray out when same as autoLabel
651  autoLabel();
652  emit widgetChanged();
653  }
654 }
655 
657 {
658  const QgsSingleBandPseudoColorRenderer* pr = dynamic_cast<const QgsSingleBandPseudoColorRenderer*>( r );
659  if ( pr )
660  {
661  mBandComboBox->setCurrentIndex( mBandComboBox->findData( pr->band() ) );
662 
663  const QgsRasterShader* rasterShader = pr->shader();
664  if ( rasterShader )
665  {
666  const QgsColorRampShader* colorRampShader = dynamic_cast<const QgsColorRampShader*>( rasterShader->rasterShaderFunction() );
667  if ( colorRampShader )
668  {
669  if ( colorRampShader->sourceColorRamp() )
670  {
671  btnColorRamp->setColorRamp( colorRampShader->sourceColorRamp() );
672  }
673  else
674  {
675  QSettings settings;
676  QString defaultPalette = settings.value( "/Raster/defaultPalette", "Spectral" ).toString();
677  btnColorRamp->setColorRampFromName( defaultPalette );
678  }
679 
680  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( colorRampShader->colorRampType() ) );
681 
682  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
683  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
684  for ( ; it != colorRampItemList.end(); ++it )
685  {
686  QgsTreeWidgetItemObject* newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
687  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
688  newItem->setBackground( ColorColumn, QBrush( it->color ) );
689  newItem->setText( LabelColumn, it->label );
690  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
691  connect( newItem, SIGNAL( itemEdited( QTreeWidgetItem*, int ) ),
692  this, SLOT( mColormapTreeWidget_itemEdited( QTreeWidgetItem*, int ) ) );
693  }
694  setUnitFromLabels();
695  mClipCheckBox->setChecked( colorRampShader->clip() );
696 
697  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( colorRampShader->classificationMode() ) );
698  mNumberOfEntriesSpinBox->setValue( colorRampShader->colorRampItemList().count() ); // some default
699  }
700  }
701  setLineEditValue( mMinLineEdit, pr->classificationMin() );
702  setLineEditValue( mMaxLineEdit, pr->classificationMax() );
703 
704  mMinMaxWidget->setFromMinMaxOrigin( pr->minMaxOrigin() );
705  }
706 }
707 
708 void QgsSingleBandPseudoColorRendererWidget::on_mBandComboBox_currentIndexChanged( int index )
709 {
710  QList<int> bands;
711  bands.append( mBandComboBox->itemData( index ).toInt() );
712  mMinMaxWidget->setBands( bands );
713 }
714 
715 void QgsSingleBandPseudoColorRendererWidget::on_mColorInterpolationComboBox_currentIndexChanged( int index )
716 {
717  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->itemData( index ).toInt() );
718 
719  mClipCheckBox->setEnabled( interpolation == QgsColorRampShader::Interpolated );
720 
721  QString valueLabel;
722  QString valueToolTip;
723  switch ( interpolation )
724  {
726  valueLabel = tr( "Value" );
727  valueToolTip = tr( "Value for color stop" );
728  break;
730  valueLabel = tr( "Value <=" );
731  valueToolTip = tr( "Maximum value for class" );
732  break;
734  valueLabel = tr( "Value =" );
735  valueToolTip = tr( "Value for color" );
736  break;
737  }
738 
739  QTreeWidgetItem* header = mColormapTreeWidget->headerItem();
740  header->setText( ValueColumn, valueLabel );
741  header->setToolTip( ValueColumn, valueToolTip );
742 
743  autoLabel();
744  emit widgetChanged();
745 }
746 
748 {
749  Q_UNUSED( bandNo );
750  QgsDebugMsg( QString( "theBandNo = %1 min = %2 max = %3" ).arg( bandNo ).arg( min ).arg( max ) );
751 
752  mDisableMinMaxWidgetRefresh = true;
753  if ( qIsNaN( min ) )
754  {
755  mMinLineEdit->clear();
756  }
757  else
758  {
759  mMinLineEdit->setText( QString::number( min ) );
760  }
761 
762  if ( qIsNaN( max ) )
763  {
764  mMaxLineEdit->clear();
765  }
766  else
767  {
768  mMaxLineEdit->setText( QString::number( max ) );
769  }
770  mDisableMinMaxWidgetRefresh = false;
771  classify();
772 }
773 
774 void QgsSingleBandPseudoColorRendererWidget::setLineEditValue( QLineEdit * lineEdit, double value )
775 {
776  QString s;
777  if ( !qIsNaN( value ) )
778  {
779  s = QString::number( value );
780  }
781  lineEdit->setText( s );
782 }
783 
784 double QgsSingleBandPseudoColorRendererWidget::lineEditValue( const QLineEdit * lineEdit ) const
785 {
786  if ( lineEdit->text().isEmpty() )
787  {
788  return std::numeric_limits<double>::quiet_NaN();
789  }
790 
791  return lineEdit->text().toDouble();
792 }
793 
794 void QgsSingleBandPseudoColorRendererWidget::resetClassifyButton()
795 {
796  mClassifyButton->setEnabled( true );
797  double min = lineEditValue( mMinLineEdit );
798  double max = lineEditValue( mMaxLineEdit );
799  if ( qIsNaN( min ) || qIsNaN( max ) || min >= max )
800  {
801  mClassifyButton->setEnabled( false );
802  }
803 }
804 
805 void QgsSingleBandPseudoColorRendererWidget::changeColor()
806 {
807  QList<QTreeWidgetItem *> itemList;
808  itemList = mColormapTreeWidget->selectedItems();
809  if ( itemList.isEmpty() )
810  {
811  return;
812  }
813  QTreeWidgetItem* firstItem = itemList.first();
814 
815  QColor newColor = QgsColorDialog::getColor( firstItem->background( ColorColumn ).color(), this, QStringLiteral( "Change color" ), true );
816  if ( newColor.isValid() )
817  {
818  Q_FOREACH ( QTreeWidgetItem *item, itemList )
819  {
820  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
821  item->setBackground( ColorColumn, QBrush( newColor ) );
822  }
823  emit widgetChanged();
824  }
825 }
826 
827 void QgsSingleBandPseudoColorRendererWidget::changeTransparency()
828 {
829  QList<QTreeWidgetItem *> itemList;
830  itemList = mColormapTreeWidget->selectedItems();
831  if ( itemList.isEmpty() )
832  {
833  return;
834  }
835  QTreeWidgetItem* firstItem = itemList.first();
836 
837  bool ok;
838  double oldTransparency = firstItem->background( ColorColumn ).color().alpha() / 255 * 100;
839  double transparency = QInputDialog::getDouble( this, tr( "Transparency" ), tr( "Change color transparency [%]" ), oldTransparency, 0.0, 100.0, 0, &ok );
840  if ( ok )
841  {
842  int newTransparency = transparency / 100 * 255;
843  Q_FOREACH ( QTreeWidgetItem *item, itemList )
844  {
845  QColor newColor = item->background( ColorColumn ).color();
846  newColor.setAlpha( newTransparency );
847  item->setBackground( ColorColumn, QBrush( newColor ) );
848  }
849  emit widgetChanged();
850  }
851 }
852 
853 void QgsSingleBandPseudoColorRendererWidget::on_mMinLineEdit_textEdited( const QString & )
854 {
855  minMaxModified();
856  classify();
857 }
858 
859 void QgsSingleBandPseudoColorRendererWidget::on_mMaxLineEdit_textEdited( const QString & )
860 {
861  minMaxModified();
862  classify();
863 }
864 
865 void QgsSingleBandPseudoColorRendererWidget::minMaxModified()
866 {
867  if ( !mDisableMinMaxWidgetRefresh )
868  {
869  mMinMaxWidget->userHasSetManualMinMaxValues();
870  }
871 }
virtual int bandCount() const =0
Get number of bands.
void setExtent(const QgsRectangle &extent)
Sets the extent to use for minimum and maximum value calculation.
static unsigned index
A rectangle specified with double values.
Definition: qgsrectangle.h:36
Interface for all raster shaders.
void setColorRampItemList(const QList< QgsColorRampShader::ColorRampItem > &list)
Set custom colormap.
Uses quantile (i.e. equal pixel) count.
A ramp shader will color a raster pixel based on a list of values ranges in a ramp.
virtual QList< QgsColorRampShader::ColorRampItem > colorTable(int bandNo) const
void colorRampChanged()
Emitted whenever a new color ramp is set for the button.
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
QList< QgsColorRampShader::ColorRampItem > colorRampItemList() const
Get the custom colormap.
QgsRasterMinMaxOrigin minMaxOrigin()
Return a QgsRasterMinMaxOrigin object with the widget values.
const QgsRasterMinMaxOrigin & minMaxOrigin() const
Returns const reference to origin of min/max values.
QgsRasterRenderer * renderer() const
void setClip(bool clip)
Sets whether the shader should not render values out of range.
QgsRasterShader * shader()
Returns the raster shader.
void setLimits(Limits limits)
Set limits.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:72
Type
Supported methods for color interpolation.
virtual void setMapCanvas(QgsMapCanvas *canvas)
Sets the map canvas associated with the widget.
QgsRasterMinMaxWidget * minMaxWidget() override
Return min/max widget when it exists.
void setMapCanvas(QgsMapCanvas *canvas) override
Sets the map canvas associated with the widget.
void widgetChanged()
Emitted when something on the widget has changed.
void setColorRampType(QgsColorRampShader::Type colorRampType)
Set the color ramp type.
This class describes the origin of min/max values.
virtual QString min(int index=0)
QgsRasterShaderFunction * rasterShaderFunction()
void setBands(const QList< int > &bands)
QString displayBandName(int band) const
Returns a band name for display. First choice is color name, otherwise band number.
Raster renderer pipe for single band pseudocolor.
void doComputations() override
Load programmatically with current values.
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), const bool allowAlpha=false)
Return a color selection from a color dialog.
void setSourceColorRamp(QgsColorRamp *colorramp)
Set the source color ramp.
void setRasterShaderFunction(QgsRasterShaderFunction *)
A public method that allows the user to set their own shader function.
Custom QgsTreeWidgetItem with extra signals when item is edited.
void setMinMaxOrigin(const QgsRasterMinMaxOrigin &origin)
Sets origin of min/max values.
Type colorRampType() const
Get the color ramp type.
void userHasSetManualMinMaxValues()
Uncheck cumulative cut, min/max, std-dev radio buttons.
QgsRectangle extent()
Return the extent selected by the user.
Assigns the color of the exact matching value in the color ramp item list.
void setMapCanvas(QgsMapCanvas *canvas)
Sets the map canvas associated with the widget.
Uses breaks from color palette.
void setClassificationMode(ClassificationMode classificationMode)
Sets classification mode.
void classify()
Executes the single band pseudo raster classficiation.
QgsSingleBandPseudoColorRendererWidget(QgsRasterLayer *layer, const QgsRectangle &extent=QgsRectangle())
bool clip() const
Returns whether the shader will clip values which are out of range.
Interpolates the color between two class breaks linearly.
void load(int bandNo, double min, double max)
signal emitted when new min/max values are computed from statistics.
virtual QString max(int index=0)
void createShader(QgsColorRamp *colorRamp=nullptr, QgsColorRampShader::Type colorRampType=QgsColorRampShader::Interpolated, QgsColorRampShader::ClassificationMode classificationMode=QgsColorRampShader::Continuous, int classes=0, bool clip=false, const QgsRectangle &extent=QgsRectangle())
Creates a color ramp shader.
QgsRasterDataProvider * dataProvider()
Returns the data provider.
ClassificationMode
Classification modes used to create the color ramp shader.
Assigns the color of the higher class for every pixel between two class breaks.
ClassificationMode classificationMode() const
Returns the classification mode.
QgsMapCanvas * mCanvas
Associated map canvas.
QgsColorRamp * sourceColorRamp() const
Get the source color ramp.
void widgetChanged()
Emitted when something on the widget has changed.
Raster renderer pipe that applies colors to a raster.
Limits limits() const
Return limits.
void doComputations()
Load programmatically with current values.
int band() const
Returns the band used by the renderer.
void loadMinMax(int bandNo, double min, double max)
called when new min/max values are loaded
Base class for raster data providers.
void setFromMinMaxOrigin(const QgsRasterMinMaxOrigin &)
Set the "source" of min/max values.