QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
qgscolorrampshaderwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscolorrampshaderwidget.cpp
3  ----------------------------
4  begin : Jun 2018
5  copyright : (C) 2018 by Peter Petrik
6  email : zilolv 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 "qgsrasterdataprovider.h"
19 
22 #include "qgsrasterlayer.h"
23 #include "qgsrasterdataprovider.h"
24 #include "qgsrastershader.h"
25 #include "qgsrasterminmaxwidget.h"
26 #include "qgstreewidgetitem.h"
27 #include "qgssettings.h"
28 #include "qgsstyle.h"
29 #include "qgscolorramp.h"
30 #include "qgscolorrampbutton.h"
31 #include "qgscolordialog.h"
32 #include "qgsrasterrendererutils.h"
33 #include "qgsfileutils.h"
34 #include "qgsguiutils.h"
35 
36 #include <QCursor>
37 #include <QPushButton>
38 #include <QInputDialog>
39 #include <QFileDialog>
40 #include <QMenu>
41 #include <QMessageBox>
42 #include <QTextStream>
43 #include <QTreeView>
44 
46  : QWidget( parent )
47 {
48  QgsSettings settings;
49 
50  setupUi( this );
51  mLoadFromBandButton->setVisible( false ); // only for raster version
52 
53  connect( mAddEntryButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mAddEntryButton_clicked );
54  connect( mDeleteEntryButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mDeleteEntryButton_clicked );
55  connect( mLoadFromBandButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mLoadFromBandButton_clicked );
56  connect( mLoadFromFileButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mLoadFromFileButton_clicked );
57  connect( mExportToFileButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mExportToFileButton_clicked );
58  connect( mUnitLineEdit, &QLineEdit::textEdited, this, &QgsColorRampShaderWidget::mUnitLineEdit_textEdited );
59  connect( mColormapTreeWidget, &QTreeWidget::itemDoubleClicked, this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemDoubleClicked );
60  connect( mColorInterpolationComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsColorRampShaderWidget::mColorInterpolationComboBox_currentIndexChanged );
61  connect( mClassificationModeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsColorRampShaderWidget::mClassificationModeComboBox_currentIndexChanged );
62 
63  contextMenu = new QMenu( tr( "Options" ), this );
64  contextMenu->addAction( tr( "Change Color…" ), this, SLOT( changeColor() ) );
65  contextMenu->addAction( tr( "Change Opacity…" ), this, SLOT( changeOpacity() ) );
66 
67  mSwatchDelegate = new QgsColorSwatchDelegate( this );
68  mColormapTreeWidget->setItemDelegateForColumn( ColorColumn, mSwatchDelegate );
69 
70 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
71  mColormapTreeWidget->setColumnWidth( ColorColumn, Qgis::UI_SCALE_FACTOR * fontMetrics().width( 'X' ) * 6.6 );
72 #else
73  mColormapTreeWidget->setColumnWidth( ColorColumn, Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 6.6 );
74 #endif
75 
76  mColormapTreeWidget->setContextMenuPolicy( Qt::CustomContextMenu );
77  mColormapTreeWidget->setSelectionMode( QAbstractItemView::ExtendedSelection );
78  connect( mColormapTreeWidget, &QTreeView::customContextMenuRequested, this, [ = ]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
79 
80  QString defaultPalette = settings.value( QStringLiteral( "Raster/defaultPalette" ), "" ).toString();
81  btnColorRamp->setColorRampFromName( defaultPalette );
82 
83  mColorInterpolationComboBox->addItem( tr( "Discrete" ), QgsColorRampShader::Discrete );
84  mColorInterpolationComboBox->addItem( tr( "Linear" ), QgsColorRampShader::Interpolated );
85  mColorInterpolationComboBox->addItem( tr( "Exact" ), QgsColorRampShader::Exact );
86  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
87 
88  mClassificationModeComboBox->addItem( tr( "Continuous" ), QgsColorRampShader::Continuous );
89  mClassificationModeComboBox->addItem( tr( "Equal Interval" ), QgsColorRampShader::EqualInterval );
90  // Quantile added only on demand
91  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::Continuous ) );
92 
93  mNumberOfEntriesSpinBox->setValue( 5 ); // some default
94 
95  mClassificationModeComboBox_currentIndexChanged( 0 );
96 
97  resetClassifyButton();
98 
99  connect( mClassificationModeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsColorRampShaderWidget::classify );
100  connect( mColorInterpolationComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsColorRampShaderWidget::classify );
101  connect( mClassifyButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::classify );
102  connect( btnColorRamp, &QgsColorRampButton::colorRampChanged, this, &QgsColorRampShaderWidget::applyColorRamp );
103  connect( mNumberOfEntriesSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsColorRampShaderWidget::classify );
104  connect( mClipCheckBox, &QAbstractButton::toggled, this, &QgsColorRampShaderWidget::widgetChanged );
105  connect( mLabelPrecisionSpinBox, qgis::overload<int>::of( &QSpinBox::valueChanged ), this, [ = ]( int )
106  {
107  autoLabel();
108  } );
109 }
110 
112 {
113  Q_ASSERT( mClassificationModeComboBox->findData( QgsColorRampShader::Quantile < 0 ) );
114  mClassificationModeComboBox->addItem( tr( "Quantile" ), QgsColorRampShader::Quantile );
115 }
116 
118 {
119  mRasterDataProvider = dp;
120  mLoadFromBandButton->setVisible( bool( mRasterDataProvider ) ); // only for raster version
121 }
122 
124 {
125  mBand = band;
126  // Set the maximum number of digits in the precision spin box
127  if ( mRasterDataProvider )
128  {
129  const int maxDigits { QgsGuiUtils::significantDigits( mRasterDataProvider->dataType( mBand ) ) };
130  mLabelPrecisionSpinBox->setMaximum( maxDigits );
131  }
132 }
133 
135 {
136  mExtent = extent;
137 }
138 
140 {
141  QgsColorRampShader colorRampShader( mMin, mMax );
142  colorRampShader.setColorRampType( static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ) );
143  colorRampShader.setClassificationMode( static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ) );
144  colorRampShader.setClip( mClipCheckBox->isChecked() );
145 
146  //iterate through mColormapTreeWidget and set colormap info of layer
147  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
148  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
149  QTreeWidgetItem *currentItem = nullptr;
150  for ( int i = 0; i < topLevelItemCount; ++i )
151  {
152  currentItem = mColormapTreeWidget->topLevelItem( i );
153  if ( !currentItem )
154  {
155  continue;
156  }
157  QgsColorRampShader::ColorRampItem newColorRampItem;
158  newColorRampItem.value = QLocale().toDouble( currentItem->text( ValueColumn ) );
159  newColorRampItem.color = currentItem->data( ColorColumn, Qt::EditRole ).value<QColor>();
160  newColorRampItem.label = currentItem->text( LabelColumn );
161  colorRampItems.append( newColorRampItem );
162  }
163  // sort the shader items
164  std::sort( colorRampItems.begin(), colorRampItems.end() );
165  colorRampShader.setColorRampItemList( colorRampItems );
166 
167  if ( !btnColorRamp->isNull() )
168  {
169  colorRampShader.setSourceColorRamp( btnColorRamp->colorRamp() );
170  }
171  return colorRampShader;
172 }
173 
174 void QgsColorRampShaderWidget::autoLabel()
175 {
176  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
177  bool discrete = interpolation == QgsColorRampShader::Discrete;
178  QString unit = mUnitLineEdit->text();
179  QString label;
180  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
181 
182  auto applyPrecision = [ = ]( const QString & value )
183  {
184  double val { QLocale().toDouble( value ) };
185  if ( mLabelPrecisionSpinBox->value() < 0 )
186  {
187  const double factor = std::pow( 10, - mLabelPrecisionSpinBox->value() );
188  val = static_cast<qlonglong>( val / factor ) * factor;
189  return QLocale().toString( val, 'f', 0 );
190  }
191  return QLocale().toString( val, 'f', mLabelPrecisionSpinBox->value() );
192  };
193 
194  QTreeWidgetItem *currentItem = nullptr;
195  for ( int i = 0; i < topLevelItemCount; ++i )
196  {
197  currentItem = mColormapTreeWidget->topLevelItem( i );
198  //If the item is null or does not have a pixel values set, skip
199  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
200  {
201  continue;
202  }
203 
204  if ( discrete )
205  {
206  if ( i == 0 )
207  {
208  label = "<= " + applyPrecision( currentItem->text( ValueColumn ) ) + unit;
209  }
210  else if ( QLocale().toDouble( currentItem->text( ValueColumn ) ) == std::numeric_limits<double>::infinity() )
211  {
212  label = "> " + applyPrecision( mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) ) + unit;
213  }
214  else
215  {
216  label = applyPrecision( mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) ) + " - " + applyPrecision( currentItem->text( ValueColumn ) ) + unit;
217  }
218  }
219  else
220  {
221  label = applyPrecision( currentItem->text( ValueColumn ) ) + unit;
222  }
223 
224  if ( currentItem->text( LabelColumn ).isEmpty() || currentItem->text( LabelColumn ) == label || currentItem->foreground( LabelColumn ).color() == QColor( Qt::gray ) )
225  {
226  currentItem->setText( LabelColumn, label );
227  currentItem->setForeground( LabelColumn, QBrush( QColor( Qt::gray ) ) );
228  }
229  }
230 }
231 
232 void QgsColorRampShaderWidget::setUnitFromLabels()
233 {
234  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
235  bool discrete = interpolation == QgsColorRampShader::Discrete;
236  QStringList allSuffixes;
237  QString label;
238  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
239  QTreeWidgetItem *currentItem = nullptr;
240  for ( int i = 0; i < topLevelItemCount; ++i )
241  {
242  currentItem = mColormapTreeWidget->topLevelItem( i );
243  //If the item is null or does not have a pixel values set, skip
244  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
245  {
246  continue;
247  }
248 
249  if ( discrete )
250  {
251  if ( i == 0 )
252  {
253  label = "<= " + currentItem->text( ValueColumn );
254  }
255  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
256  {
257  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn );
258  }
259  else
260  {
261  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn );
262  }
263  }
264  else
265  {
266  label = currentItem->text( ValueColumn );
267  }
268 
269  if ( currentItem->text( LabelColumn ).startsWith( label ) )
270  {
271  allSuffixes.append( currentItem->text( LabelColumn ).mid( label.length() ) );
272  }
273  }
274  // find most common suffix
275  QStringList suffixes = QStringList( allSuffixes );
276  suffixes.removeDuplicates();
277  int max = 0;
278  QString unit;
279  for ( int i = 0; i < suffixes.count(); ++i )
280  {
281  int n = allSuffixes.count( suffixes[i] );
282  if ( n > max )
283  {
284  max = n;
285  unit = suffixes[i];
286  }
287  }
288  // Set this suffix as unit if at least used twice
289  if ( max >= 2 )
290  {
291  mUnitLineEdit->setText( unit );
292  }
293 }
294 
295 void QgsColorRampShaderWidget::mAddEntryButton_clicked()
296 {
297  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
298  newItem->setText( ValueColumn, QStringLiteral( "0" ) );
299  newItem->setData( ColorColumn, Qt::EditRole, QColor( Qt::magenta ) );
300  newItem->setText( LabelColumn, QString() );
301  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
302  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
303  this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited );
304  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
305  autoLabel();
306 
308  emit widgetChanged();
309 }
310 
311 void QgsColorRampShaderWidget::mDeleteEntryButton_clicked()
312 {
313  QList<QTreeWidgetItem *> itemList;
314  itemList = mColormapTreeWidget->selectedItems();
315  if ( itemList.isEmpty() )
316  {
317  return;
318  }
319 
320  const auto constItemList = itemList;
321  for ( QTreeWidgetItem *item : constItemList )
322  {
323  delete item;
324  }
325 
327  emit widgetChanged();
328 }
329 
331 {
332  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
333  if ( !ramp || std::isnan( mMin ) || std::isnan( mMax ) )
334  {
335  return;
336  }
337 
338  std::unique_ptr< QgsColorRampShader > colorRampShader( new QgsColorRampShader(
339  mMin, mMax,
340  ramp.release(),
341  static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ),
342  static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ) )
343  );
344 
345  // only for Quantile we need band and provider and extent
346  colorRampShader->classifyColorRamp( mNumberOfEntriesSpinBox->value(),
347  mBand,
348  mExtent,
349  mRasterDataProvider );
350  colorRampShader->setClip( mClipCheckBox->isChecked() );
351 
352  mColormapTreeWidget->clear();
353 
354  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
355  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
356  for ( ; it != colorRampItemList.end(); ++it )
357  {
358  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
359  newItem->setText( ValueColumn, QLocale().toString( it->value, 'g', 15 ) );
360  newItem->setData( ColorColumn, Qt::EditRole, it->color );
361  newItem->setText( LabelColumn, QString() ); // Labels will be populated in autoLabel()
362  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
363  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
364  this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited );
365  }
366 
367  mClipCheckBox->setChecked( colorRampShader->clip() );
368 
369  autoLabel();
370  emit widgetChanged();
371 }
372 
373 void QgsColorRampShaderWidget::mClassificationModeComboBox_currentIndexChanged( int index )
374 {
375  QgsColorRampShader::ClassificationMode mode = static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->itemData( index ).toInt() );
376  mNumberOfEntriesSpinBox->setEnabled( mode != QgsColorRampShader::Continuous );
377  emit classificationModeChanged( mode );
378 }
379 
380 void QgsColorRampShaderWidget::applyColorRamp()
381 {
382  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
383  if ( !ramp )
384  {
385  return;
386  }
387 
388  if ( !btnColorRamp->colorRampName().isEmpty() )
389  {
390  // Remember last used color ramp
391  QgsSettings settings;
392  settings.setValue( QStringLiteral( "Raster/defaultPalette" ), btnColorRamp->colorRampName() );
393  }
394 
395  bool enableContinuous = ( ramp->count() > 0 );
396  mClassificationModeComboBox->setEnabled( enableContinuous );
397  if ( !enableContinuous )
398  {
399  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::EqualInterval ) );
400  }
401 
402  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
403  if ( topLevelItemCount > 0 )
404  {
405  // We need to have valid min/max values here. If we haven't, load from colormap
406  double min, max;
407  if ( std::isnan( mMin ) || std::isnan( mMax ) )
408  {
409  colormapMinMax( min, max );
410  }
411  else
412  {
413  min = mMin;
414  max = mMax;
415  }
416 
417  // if the list values has been customized, maintain pre-existing values
418  QTreeWidgetItem *currentItem = nullptr;
419  for ( int i = 0; i < topLevelItemCount; ++i )
420  {
421  currentItem = mColormapTreeWidget->topLevelItem( i );
422  if ( !currentItem )
423  {
424  continue;
425  }
426 
427  double value = QLocale().toDouble( currentItem->text( ValueColumn ) );
428  double position = ( value - min ) / ( max - min );
429  whileBlocking( static_cast<QgsTreeWidgetItemObject *>( currentItem ) )->setData( ColorColumn, Qt::EditRole, ramp->color( position ) );
430  }
431 
432  emit widgetChanged();
433  }
434  else
435  {
436  classify();
437  }
438 }
439 
440 void QgsColorRampShaderWidget::populateColormapTreeWidget( const QList<QgsColorRampShader::ColorRampItem> &colorRampItems )
441 {
442  mColormapTreeWidget->clear();
443  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItems.constBegin();
444  for ( ; it != colorRampItems.constEnd(); ++it )
445  {
446  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
447  newItem->setText( ValueColumn, QLocale().toString( it->value, 'g', 15 ) );
448  newItem->setData( ColorColumn, Qt::EditRole, it->color );
449  newItem->setText( LabelColumn, it->label );
450  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
451  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
452  this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited );
453  }
454  setUnitFromLabels();
455 }
456 
457 void QgsColorRampShaderWidget::mLoadFromBandButton_clicked()
458 {
459  if ( !mRasterDataProvider )
460  return;
461 
462  QList<QgsColorRampShader::ColorRampItem> colorRampList = mRasterDataProvider->colorTable( mBand );
463  if ( !colorRampList.isEmpty() )
464  {
465  populateColormapTreeWidget( colorRampList );
466  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
467  }
468  else
469  {
470  QMessageBox::warning( this, tr( "Load Color Map" ), tr( "The color map for band %1 has no entries." ).arg( mBand ) );
471  }
473  emit widgetChanged();
474 }
475 
476 void QgsColorRampShaderWidget::mLoadFromFileButton_clicked()
477 {
478  QgsSettings settings;
479  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
480  const QString fileName = QFileDialog::getOpenFileName( this, tr( "Load Color Map from File" ), lastDir, tr( "Textfile (*.txt)" ) );
481  if ( fileName.isEmpty() )
482  return;
483 
484  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
486  QStringList errors;
487  if ( QgsRasterRendererUtils::parseColorMapFile( fileName, colorRampItems, type, errors ) )
488  {
489  //clear the current tree
490  mColormapTreeWidget->clear();
491 
492  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( type ) );
493 
494  populateColormapTreeWidget( colorRampItems );
495 
496  if ( !errors.empty() )
497  {
498  QMessageBox::warning( this, tr( "Load Color Map from File" ), tr( "The following lines contained errors\n\n" ) + errors.join( '\n' ) );
499  }
500  }
501  else
502  {
503  const QString error = tr( "An error occurred while reading the color map\n\n" ) + errors.join( '\n' );
504  QMessageBox::warning( this, tr( "Load Color Map from File" ), error );
505  }
506 
507  QFileInfo fileInfo( fileName );
508  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
509 
511  emit widgetChanged();
512 }
513 
514 void QgsColorRampShaderWidget::mExportToFileButton_clicked()
515 {
516  QgsSettings settings;
517  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
518  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Color Map as File" ), lastDir, tr( "Textfile (*.txt)" ) );
519  if ( fileName.isEmpty() )
520  return;
521 
522  fileName = QgsFileUtils::ensureFileNameHasExtension( fileName, QStringList() << QStringLiteral( "txt" ) );
523 
524  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
525  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
526  for ( int i = 0; i < topLevelItemCount; ++i )
527  {
528  QTreeWidgetItem *currentItem = mColormapTreeWidget->topLevelItem( i );
529  if ( !currentItem )
530  {
531  continue;
532  }
533 
535  item.value = QLocale().toDouble( currentItem->text( ValueColumn ) );
536  item.color = currentItem->data( ColorColumn, Qt::EditRole ).value<QColor>();
537  item.label = currentItem->text( LabelColumn );
538  colorRampItems << item;
539  }
540 
541  if ( !QgsRasterRendererUtils::saveColorMapFile( fileName, colorRampItems, static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ) ) )
542  {
543  QMessageBox::warning( this, tr( "Save Color Map as File" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
544  }
545 
546  QFileInfo fileInfo( fileName );
547  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
548 }
549 
550 void QgsColorRampShaderWidget::mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem *item, int column )
551 {
552  if ( !item )
553  {
554  return;
555  }
556 
557  if ( column == LabelColumn )
558  {
559  // Set text color to default black, which signifies a manually edited label
560  item->setForeground( LabelColumn, QBrush() );
561  }
562 }
563 
564 void QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited( QTreeWidgetItem *item, int column )
565 {
566  Q_UNUSED( item )
567 
568  switch ( column )
569  {
570  case ValueColumn:
571  {
572  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
573  autoLabel();
574 
576 
577  emit widgetChanged();
578  break;
579  }
580 
581  case LabelColumn:
582  {
583  // call autoLabel to fill when empty or gray out when same as autoLabel
584  autoLabel();
585  emit widgetChanged();
586  break;
587  }
588 
589  case ColorColumn:
590  {
592  emit widgetChanged();
593  break;
594  }
595  }
596 }
597 
599 {
600  // Those objects are connected to classify() the color ramp shader if they change, or call widget change
601  // need to block them to avoid to classify and to alter the color ramp, or to call duplicate widget change
602  whileBlocking( mClipCheckBox )->setChecked( colorRampShader.clip() );
603  whileBlocking( mColorInterpolationComboBox )->setCurrentIndex( mColorInterpolationComboBox->findData( colorRampShader.colorRampType() ) );
604  mColorInterpolationComboBox_currentIndexChanged( mColorInterpolationComboBox->currentIndex() );
605  whileBlocking( mClassificationModeComboBox )->setCurrentIndex( mClassificationModeComboBox->findData( colorRampShader.classificationMode() ) );
606  mClassificationModeComboBox_currentIndexChanged( mClassificationModeComboBox->currentIndex() );
607  whileBlocking( mNumberOfEntriesSpinBox )->setValue( colorRampShader.colorRampItemList().count() ); // some default
608 
609  if ( colorRampShader.sourceColorRamp() )
610  {
611  whileBlocking( btnColorRamp )->setColorRamp( colorRampShader.sourceColorRamp() );
612  }
613  else
614  {
615  QgsSettings settings;
616  QString defaultPalette = settings.value( QStringLiteral( "/Raster/defaultPalette" ), "Spectral" ).toString();
617  btnColorRamp->setColorRampFromName( defaultPalette );
618  }
619 
620  populateColormapTreeWidget( colorRampShader.colorRampItemList() );
621 
622  emit widgetChanged();
623 }
624 
625 void QgsColorRampShaderWidget::mColorInterpolationComboBox_currentIndexChanged( int index )
626 {
627  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->itemData( index ).toInt() );
628 
629  mClipCheckBox->setEnabled( interpolation == QgsColorRampShader::Interpolated );
630 
631  QString valueLabel;
632  QString valueToolTip;
633  switch ( interpolation )
634  {
636  valueLabel = tr( "Value" );
637  valueToolTip = tr( "Value for color stop" );
638  break;
640  valueLabel = tr( "Value <=" );
641  valueToolTip = tr( "Maximum value for class" );
642  break;
644  valueLabel = tr( "Value =" );
645  valueToolTip = tr( "Value for color" );
646  break;
647  }
648 
649  QTreeWidgetItem *header = mColormapTreeWidget->headerItem();
650  header->setText( ValueColumn, valueLabel );
651  header->setToolTip( ValueColumn, valueToolTip );
652 
653  autoLabel();
654  emit widgetChanged();
655 }
656 
658 {
659  if ( !qgsDoubleNear( mMin, min ) || !qgsDoubleNear( mMax, max ) )
660  {
661  setMinimumMaximum( min, max );
662  classify();
663  }
664 }
665 
666 void QgsColorRampShaderWidget::setMinimumMaximum( double min, double max )
667 {
668  mMin = min;
669  mMax = max;
670  resetClassifyButton();
671 }
672 
674 {
675  return mMin;
676 }
677 
679 {
680  return mMax;
681 }
682 
683 bool QgsColorRampShaderWidget::colormapMinMax( double &min, double &max ) const
684 {
685  QTreeWidgetItem *item = mColormapTreeWidget->topLevelItem( 0 );
686  if ( !item )
687  {
688  return false;
689  }
690 
691  min = QLocale().toDouble( item->text( ValueColumn ) );
692  item = mColormapTreeWidget->topLevelItem( mColormapTreeWidget->topLevelItemCount() - 1 );
693  max = QLocale().toDouble( item->text( ValueColumn ) );
694 
695  return true;
696 }
697 
699 {
700  double min = 0, max = 0;
701  if ( ! colormapMinMax( min, max ) )
702  {
703  return;
704  }
705 
706  if ( !qgsDoubleNear( mMin, min ) || !qgsDoubleNear( mMax, max ) )
707  {
708  mMin = min;
709  mMax = max;
710  emit minimumMaximumChangedFromTree( min, max );
711  }
712 }
713 
714 void QgsColorRampShaderWidget::resetClassifyButton()
715 {
716  mClassifyButton->setEnabled( true );
717  if ( std::isnan( mMin ) || std::isnan( mMax ) || mMin >= mMax )
718  {
719  mClassifyButton->setEnabled( false );
720  }
721 }
722 
723 void QgsColorRampShaderWidget::changeColor()
724 {
725  QList<QTreeWidgetItem *> itemList;
726  itemList = mColormapTreeWidget->selectedItems();
727  if ( itemList.isEmpty() )
728  {
729  return;
730  }
731  QTreeWidgetItem *firstItem = itemList.first();
732 
733  QColor currentColor = firstItem->data( ColorColumn, Qt::EditRole ).value<QColor>();
734  QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast< QWidget * >( parent() ) );
735  if ( panel && panel->dockMode() )
736  {
738  colorWidget->setPanelTitle( tr( "Select Color" ) );
739  colorWidget->setAllowOpacity( true );
740  connect( colorWidget, &QgsCompoundColorWidget::currentColorChanged, this, [ = ]( const QColor & newColor )
741  {
742  for ( QTreeWidgetItem *item : qgis::as_const( itemList ) )
743  {
744  item->setData( ColorColumn, Qt::EditRole, newColor );
745  }
746 
748  emit widgetChanged();
749  } );
750  panel->openPanel( colorWidget );
751  }
752  else
753  {
754  // modal dialog version... yuck
755  QColor newColor = QgsColorDialog::getColor( currentColor, this, QStringLiteral( "Change Color" ), true );
756  if ( newColor.isValid() )
757  {
758  for ( QTreeWidgetItem *item : qgis::as_const( itemList ) )
759  {
760  item->setData( ColorColumn, Qt::EditRole, newColor );
761  }
762 
764  emit widgetChanged();
765  }
766  }
767 }
768 
769 void QgsColorRampShaderWidget::changeOpacity()
770 {
771  QList<QTreeWidgetItem *> itemList;
772  itemList = mColormapTreeWidget->selectedItems();
773  if ( itemList.isEmpty() )
774  {
775  return;
776  }
777  QTreeWidgetItem *firstItem = itemList.first();
778 
779  bool ok;
780  double oldOpacity = firstItem->data( ColorColumn, Qt::EditRole ).value<QColor>().alpha() / 255 * 100;
781  double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
782  if ( ok )
783  {
784  int newOpacity = static_cast<int>( opacity / 100 * 255 );
785  const auto constItemList = itemList;
786  for ( QTreeWidgetItem *item : constItemList )
787  {
788  QColor newColor = item->data( ColorColumn, Qt::EditRole ).value<QColor>();
789  newColor.setAlpha( newOpacity );
790  item->setData( ColorColumn, Qt::EditRole, newColor );
791  }
792 
794  emit widgetChanged();
795  }
796 }
QgsCompoundColorWidget::setAllowOpacity
void setAllowOpacity(bool allowOpacity)
Sets whether opacity modification (transparency) is permitted for the color dialog.
Definition: qgscompoundcolorwidget.cpp:307
QgsColorRampShaderWidget::classify
void classify()
Executes the single band pseudo raster classification.
Definition: qgscolorrampshaderwidget.cpp:330
QgsColorRampShader::EqualInterval
@ EqualInterval
Uses equal interval.
Definition: qgscolorrampshader.h:56
QgsColorRampShaderWidget::QgsColorRampShaderWidget
QgsColorRampShaderWidget(QWidget *parent=nullptr)
Creates new color ramp shader widget.
Definition: qgscolorrampshaderwidget.cpp:45
QgsColorRampShader::setSourceColorRamp
void setSourceColorRamp(QgsColorRamp *colorramp)
Set the source color ramp.
Definition: qgscolorrampshader.cpp:131
qgsrasterlayer.h
QgsColorRampShaderWidget::setRasterBand
void setRasterBand(int band)
Sets raster band, only when used for raster layer.
Definition: qgscolorrampshaderwidget.cpp:123
QgsRasterDataProvider::dataType
Qgis::DataType dataType(int bandNo) const override=0
Returns data type for the band specified by number.
QgsSettings::value
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
Definition: qgssettings.cpp:174
QgsColorRampShader::setClassificationMode
void setClassificationMode(ClassificationMode classificationMode)
Sets classification mode.
Definition: qgscolorrampshader.h:184
QgsColorSwatchDelegate
A delegate for showing a color swatch in a list.
Definition: qgscolorschemelist.h:37
qgsrasterrendererutils.h
QgsPanelWidget::findParentPanel
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
Definition: qgspanelwidget.cpp:49
QgsColorRampShaderWidget::setRasterDataProvider
void setRasterDataProvider(QgsRasterDataProvider *dp)
Associates raster with the widget, only when used for raster layer.
Definition: qgscolorrampshaderwidget.cpp:117
QgsColorRampShaderWidget::populateColormapTreeWidget
void populateColormapTreeWidget(const QList< QgsColorRampShader::ColorRampItem > &colorRampItems)
Populates color ramp tree from ramp items.
Definition: qgscolorrampshaderwidget.cpp:440
QgsColorRampShaderWidget::minimumMaximumChangedFromTree
void minimumMaximumChangedFromTree(double minimum, double maximum)
Color ramp tree has changed.
QgsSettings
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QgsColorRampShader
A ramp shader will color a raster pixel based on a list of values ranges in a ramp.
Definition: qgscolorrampshader.h:40
qgssinglebandpseudocolorrenderer.h
QgsColorRampShader::Type
Type
Supported methods for color interpolation.
Definition: qgscolorrampshader.h:46
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:42
QgsColorRampShaderWidget::setMinimumMaximumAndClassify
void setMinimumMaximumAndClassify(double minimum, double maximum)
Sets min max and classify color tree.
Definition: qgscolorrampshaderwidget.cpp:657
QgsCompoundColorWidget
A custom QGIS widget for selecting a color, including options for selecting colors via hue wheel,...
Definition: qgscompoundcolorwidget.h:34
QgsColorRampShader::Quantile
@ Quantile
Uses quantile (i.e. equal pixel) count.
Definition: qgscolorrampshader.h:57
QgsColorRampShaderWidget::shader
QgsColorRampShader shader() const
Returns shared function used in the renderer.
Definition: qgscolorrampshaderwidget.cpp:139
QgsCompoundColorWidget::LayoutVertical
@ LayoutVertical
Use a narrower, vertically stacked layout.
Definition: qgscompoundcolorwidget.h:44
QgsColorRampShader::Discrete
@ Discrete
Assigns the color of the higher class for every pixel between two class breaks.
Definition: qgscolorrampshader.h:48
QgsTreeWidgetItemObject::setData
void setData(int column, int role, const QVariant &value) override
Sets the value for the item's column and role to the given value.
Definition: qgstreewidgetitem.cpp:139
QgsCompoundColorWidget::currentColorChanged
void currentColorChanged(const QColor &color)
Emitted when the dialog's color changes.
QgsColorRampShader::ColorRampItem::color
QColor color
Definition: qgscolorrampshader.h:98
QgsColorRampShader::setColorRampType
void setColorRampType(QgsColorRampShader::Type colorRampType)
Sets the color ramp type.
Definition: qgscolorrampshader.cpp:100
QgsPanelWidget
Base class for any widget that can be shown as a inline panel.
Definition: qgspanelwidget.h:30
QgsColorRampShader::Continuous
@ Continuous
Uses breaks from color palette.
Definition: qgscolorrampshader.h:55
QgsColorRampShader::sourceColorRamp
QgsColorRamp * sourceColorRamp() const
Returns the source color ramp.
Definition: qgscolorrampshader.cpp:126
QgsColorRampShader::setClip
void setClip(bool clip)
Sets whether the shader should not render values out of range.
Definition: qgscolorrampshader.h:194
QgsColorRampShader::ColorRampItem
Definition: qgscolorrampshader.h:86
whileBlocking
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:262
QgsColorRampShader::Exact
@ Exact
Assigns the color of the exact matching value in the color ramp item list.
Definition: qgscolorrampshader.h:49
QgsColorRampShaderWidget::setExtent
void setExtent(const QgsRectangle &extent)
Sets extent, only when used for raster layer.
Definition: qgscolorrampshaderwidget.cpp:134
qgscolorramp.h
qgsDoubleNear
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:315
QgsColorRampShaderWidget::maximum
double maximum() const
Gets max value.
Definition: qgscolorrampshaderwidget.cpp:678
Qgis::UI_SCALE_FACTOR
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:182
QgsColorRampShaderWidget::classificationModeChanged
void classificationModeChanged(QgsColorRampShader::ClassificationMode mode)
Classification mode changed.
QgsColorRampShader::setColorRampItemList
void setColorRampItemList(const QList< QgsColorRampShader::ColorRampItem > &list)
Sets a custom colormap.
Definition: qgscolorrampshader.cpp:92
QgsSettings::setValue
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Definition: qgssettings.cpp:289
QgsRasterDataProvider::colorTable
virtual QList< QgsColorRampShader::ColorRampItem > colorTable(int bandNo) const
Definition: qgsrasterdataprovider.h:262
QgsColorRampShaderWidget::setMinimumMaximum
void setMinimumMaximum(double minimum, double maximum)
Sets min max.
Definition: qgscolorrampshaderwidget.cpp:666
QgsPanelWidget::setPanelTitle
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
Definition: qgspanelwidget.h:44
qgsfileutils.h
QgsColorRampShaderWidget::setFromShader
void setFromShader(const QgsColorRampShader &colorRampShader)
Sets widget state from the color ramp shader.
Definition: qgscolorrampshaderwidget.cpp:598
qgsstyle.h
qgsrastershader.h
QgsColorRampShader::colorRampItemList
QList< QgsColorRampShader::ColorRampItem > colorRampItemList() const
Returns the custom colormap.
Definition: qgscolorrampshader.h:105
QgsColorRampShader::colorRampType
Type colorRampType() const
Returns the color ramp type.
Definition: qgscolorrampshader.h:108
qgscolordialog.h
QgsTreeWidgetItemObject::itemEdited
void itemEdited(QTreeWidgetItem *item, int column)
Emitted when the contents of the column in the specified item has been edited by the user.
QgsColorRampShader::ClassificationMode
ClassificationMode
Classification modes used to create the color ramp shader.
Definition: qgscolorrampshader.h:54
QgsFileUtils::ensureFileNameHasExtension
static QString ensureFileNameHasExtension(const QString &fileName, const QStringList &extensions)
Ensures that a fileName ends with an extension from the provided list of extensions.
Definition: qgsfileutils.cpp:59
QgsRasterRendererUtils::saveColorMapFile
static bool saveColorMapFile(const QString &path, const QList< QgsColorRampShader::ColorRampItem > &items, QgsColorRampShader::Type type)
Exports a list of color ramp items and ramp shader type to a color map file at the specified path.
Definition: qgsrasterrendererutils.cpp:96
QgsTreeWidgetItemObject
Custom QgsTreeWidgetItem with extra signals when item is edited.
Definition: qgstreewidgetitem.h:163
QgsColorRampShaderWidget::widgetChanged
void widgetChanged()
Widget changed.
qgstreewidgetitem.h
QgsGuiUtils::significantDigits
int significantDigits(const Qgis::DataType rasterDataType)
Returns the maximum number of significant digits a for the given rasterDataType.
Definition: qgsguiutils.cpp:285
qgsrasterminmaxwidget.h
QgsColorRampShader::clip
bool clip() const
Returns whether the shader will clip values which are out of range.
Definition: qgscolorrampshader.h:200
qgssettings.h
QgsColorRampShader::classificationMode
ClassificationMode classificationMode() const
Returns the classification mode.
Definition: qgscolorrampshader.h:187
QgsColorRampShaderWidget::loadMinimumMaximumFromTree
void loadMinimumMaximumFromTree()
Loads min and max values from color ramp tree.
Definition: qgscolorrampshaderwidget.cpp:698
QgsColorRampShaderWidget::minimum
double minimum() const
Gets min value.
Definition: qgscolorrampshaderwidget.cpp:673
QgsColorRampShader::ColorRampItem::label
QString label
Definition: qgscolorrampshader.h:96
qgscolorrampbutton.h
qgsguiutils.h
qgscolorrampshaderwidget.h
QgsColorRampShaderWidget::initializeForUseWithRasterLayer
void initializeForUseWithRasterLayer()
Allows quantile classification mode for raster layers.
Definition: qgscolorrampshaderwidget.cpp:111
QgsColorRampShader::Interpolated
@ Interpolated
Interpolates the color between two class breaks linearly.
Definition: qgscolorrampshader.h:47
QgsRasterDataProvider
Base class for raster data providers.
Definition: qgsrasterdataprovider.h:89
QgsColorRampShader::ColorRampItem::value
double value
Definition: qgscolorrampshader.h:97
QgsColorDialog::getColor
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), bool allowOpacity=false)
Returns a color selection from a color dialog.
Definition: qgscolordialog.cpp:82
qgsrasterdataprovider.h
QgsColorRampButton::colorRampChanged
void colorRampChanged()
Emitted whenever a new color ramp is set for the button.
QgsRasterRendererUtils::parseColorMapFile
static bool parseColorMapFile(const QString &path, QList< QgsColorRampShader::ColorRampItem > &items, QgsColorRampShader::Type &type, QStringList &errors)
Parses an exported color map file at the specified path and extracts the stored color ramp items and ...
Definition: qgsrasterrendererutils.cpp:24