QGIS API Documentation  3.15.0-Master (a49cb7c9f3)
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 }
double maximum() const
Gets max value.
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), bool allowOpacity=false)
Returns a color selection from a color dialog.
A rectangle specified with double values.
Definition: qgsrectangle.h:41
void classificationModeChanged(QgsColorRampShader::ClassificationMode mode)
Classification mode changed.
void setColorRampItemList(const QList< QgsColorRampShader::ColorRampItem > &list)
Sets a custom colormap.
Uses quantile (i.e. equal pixel) count.
void initializeForUseWithRasterLayer()
Allows quantile classification mode for raster layers.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:182
This class is a composition of two QSettings instances:
Definition: qgssettings.h:61
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.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setMinimumMaximum(double minimum, double maximum)
Sets min max.
QgsColorRampShader shader() const
Returns shared function used in the renderer.
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
QList< QgsColorRampShader::ColorRampItem > colorRampItemList() const
Returns the custom colormap.
void setData(int column, int role, const QVariant &value) override
Sets the value for the item&#39;s column and role to the given value.
void widgetChanged()
Widget changed.
Base class for any widget that can be shown as a inline panel.
QgsColorRampShaderWidget(QWidget *parent=nullptr)
Creates new color ramp shader widget.
void setClip(bool clip)
Sets whether the shader should not render values out of range.
double minimum() const
Gets min value.
Type
Supported methods for color interpolation.
void setRasterDataProvider(QgsRasterDataProvider *dp)
Associates raster with the widget, only when used for raster layer.
void setColorRampType(QgsColorRampShader::Type colorRampType)
Sets the color ramp type.
void populateColormapTreeWidget(const QList< QgsColorRampShader::ColorRampItem > &colorRampItems)
Populates color ramp tree from ramp items.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget...
A custom QGIS widget for selecting a color, including options for selecting colors via hue wheel...
void setMinimumMaximumAndClassify(double minimum, double maximum)
Sets min max and classify color tree.
void setSourceColorRamp(QgsColorRamp *colorramp)
Set the source color ramp.
Custom QgsTreeWidgetItem with extra signals when item is edited.
void setExtent(const QgsRectangle &extent)
Sets extent, only when used for raster layer.
void loadMinimumMaximumFromTree()
Loads min and max values from color ramp tree.
Type colorRampType() const
Returns the color ramp type.
void setRasterBand(int band)
Sets raster band, only when used for raster layer.
void setAllowOpacity(bool allowOpacity)
Sets whether opacity modification (transparency) is permitted for the color dialog.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:262
Assigns the color of the exact matching value in the color ramp item list.
Uses breaks from color palette.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void currentColorChanged(const QColor &color)
Emitted when the dialog&#39;s color changes.
void setClassificationMode(ClassificationMode classificationMode)
Sets classification mode.
void setFromShader(const QgsColorRampShader &colorRampShader)
Sets widget state from the color ramp shader.
bool clip() const
Returns whether the shader will clip values which are out of range.
void itemEdited(QTreeWidgetItem *item, int column)
Emitted when the contents of the column in the specified item has been edited by the user...
Interpolates the color between two class breaks linearly.
ClassificationMode
Classification modes used to create the color ramp shader.
Assigns the color of the higher class for every pixel between two class breaks.
void classify()
Executes the single band pseudo raster classification.
ClassificationMode classificationMode() const
Returns the classification mode.
QgsColorRamp * sourceColorRamp() const
Returns the source color ramp.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
Use a narrower, vertically stacked layout.
A delegate for showing a color swatch in a list.
void minimumMaximumChangedFromTree(double minimum, double maximum)
Color ramp tree has changed.
Base class for raster data providers.