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