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