QGIS API Documentation  3.12.1-București (121cc00ff0)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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( mClassifyButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::classify );
98  connect( btnColorRamp, &QgsColorRampButton::colorRampChanged, this, &QgsColorRampShaderWidget::applyColorRamp );
99  connect( mNumberOfEntriesSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsColorRampShaderWidget::classify );
100  connect( mClipCheckBox, &QAbstractButton::toggled, this, &QgsColorRampShaderWidget::widgetChanged );
101 }
102 
104 {
105  Q_ASSERT( mClassificationModeComboBox->findData( QgsColorRampShader::Quantile < 0 ) );
106  mClassificationModeComboBox->addItem( tr( "Quantile" ), QgsColorRampShader::Quantile );
107 }
108 
110 {
111  mRasterDataProvider = dp;
112  mLoadFromBandButton->setVisible( bool( mRasterDataProvider ) ); // only for raster version
113 }
114 
116 {
117  mBand = band;
118 }
119 
121 {
122  mExtent = extent;
123 }
124 
126 {
127  QgsColorRampShader colorRampShader( mMin, mMax );
128  colorRampShader.setColorRampType( static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ) );
129  colorRampShader.setClassificationMode( static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ) );
130  colorRampShader.setClip( mClipCheckBox->isChecked() );
131 
132  //iterate through mColormapTreeWidget and set colormap info of layer
133  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
134  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
135  QTreeWidgetItem *currentItem = nullptr;
136  for ( int i = 0; i < topLevelItemCount; ++i )
137  {
138  currentItem = mColormapTreeWidget->topLevelItem( i );
139  if ( !currentItem )
140  {
141  continue;
142  }
143  QgsColorRampShader::ColorRampItem newColorRampItem;
144  newColorRampItem.value = currentItem->text( ValueColumn ).toDouble();
145  newColorRampItem.color = currentItem->data( ColorColumn, Qt::EditRole ).value<QColor>();
146  newColorRampItem.label = currentItem->text( LabelColumn );
147  colorRampItems.append( newColorRampItem );
148  }
149  // sort the shader items
150  std::sort( colorRampItems.begin(), colorRampItems.end() );
151  colorRampShader.setColorRampItemList( colorRampItems );
152 
153  if ( !btnColorRamp->isNull() )
154  {
155  colorRampShader.setSourceColorRamp( btnColorRamp->colorRamp() );
156  }
157  return colorRampShader;
158 }
159 
160 void QgsColorRampShaderWidget::autoLabel()
161 {
162  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
163  bool discrete = interpolation == QgsColorRampShader::Discrete;
164  QString unit = mUnitLineEdit->text();
165  QString label;
166  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
167  QTreeWidgetItem *currentItem = nullptr;
168  for ( int i = 0; i < topLevelItemCount; ++i )
169  {
170  currentItem = mColormapTreeWidget->topLevelItem( i );
171  //If the item is null or does not have a pixel values set, skip
172  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
173  {
174  continue;
175  }
176 
177  if ( discrete )
178  {
179  if ( i == 0 )
180  {
181  label = "<= " + currentItem->text( ValueColumn ) + unit;
182  }
183  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
184  {
185  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + unit;
186  }
187  else
188  {
189  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn ) + unit;
190  }
191  }
192  else
193  {
194  label = currentItem->text( ValueColumn ) + unit;
195  }
196 
197  if ( currentItem->text( LabelColumn ).isEmpty() || currentItem->text( LabelColumn ) == label || currentItem->foreground( LabelColumn ).color() == QColor( Qt::gray ) )
198  {
199  currentItem->setText( LabelColumn, label );
200  currentItem->setForeground( LabelColumn, QBrush( QColor( Qt::gray ) ) );
201  }
202  }
203 }
204 
205 void QgsColorRampShaderWidget::setUnitFromLabels()
206 {
207  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
208  bool discrete = interpolation == QgsColorRampShader::Discrete;
209  QStringList allSuffixes;
210  QString label;
211  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
212  QTreeWidgetItem *currentItem = nullptr;
213  for ( int i = 0; i < topLevelItemCount; ++i )
214  {
215  currentItem = mColormapTreeWidget->topLevelItem( i );
216  //If the item is null or does not have a pixel values set, skip
217  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
218  {
219  continue;
220  }
221 
222  if ( discrete )
223  {
224  if ( i == 0 )
225  {
226  label = "<= " + currentItem->text( ValueColumn );
227  }
228  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
229  {
230  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn );
231  }
232  else
233  {
234  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn );
235  }
236  }
237  else
238  {
239  label = currentItem->text( ValueColumn );
240  }
241 
242  if ( currentItem->text( LabelColumn ).startsWith( label ) )
243  {
244  allSuffixes.append( currentItem->text( LabelColumn ).mid( label.length() ) );
245  }
246  }
247  // find most common suffix
248  QStringList suffixes = QStringList( allSuffixes );
249  suffixes.removeDuplicates();
250  int max = 0;
251  QString unit;
252  for ( int i = 0; i < suffixes.count(); ++i )
253  {
254  int n = allSuffixes.count( suffixes[i] );
255  if ( n > max )
256  {
257  max = n;
258  unit = suffixes[i];
259  }
260  }
261  // Set this suffix as unit if at least used twice
262  if ( max >= 2 )
263  {
264  mUnitLineEdit->setText( unit );
265  }
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  mClipCheckBox->setChecked( colorRampShader->clip() );
341 
342  autoLabel();
343  emit widgetChanged();
344 }
345 
346 void QgsColorRampShaderWidget::mClassificationModeComboBox_currentIndexChanged( int index )
347 {
348  QgsColorRampShader::ClassificationMode mode = static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->itemData( index ).toInt() );
349  mNumberOfEntriesSpinBox->setEnabled( mode != QgsColorRampShader::Continuous );
350  emit classificationModeChanged( mode );
351 
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  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  emit widgetChanged();
418 }
419 
420 void QgsColorRampShaderWidget::mLoadFromBandButton_clicked()
421 {
422  if ( !mRasterDataProvider )
423  return;
424 
425  QList<QgsColorRampShader::ColorRampItem> colorRampList = mRasterDataProvider->colorTable( mBand );
426  if ( !colorRampList.isEmpty() )
427  {
428  populateColormapTreeWidget( colorRampList );
429  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
430  }
431  else
432  {
433  QMessageBox::warning( this, tr( "Load Color Map" ), tr( "The color map for band %1 has no entries." ).arg( mBand ) );
434  }
436  emit widgetChanged();
437 }
438 
439 void QgsColorRampShaderWidget::mLoadFromFileButton_clicked()
440 {
441  int lineCounter = 0;
442  bool importError = false;
443  QString badLines;
444  QgsSettings settings;
445  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
446  QString fileName = QFileDialog::getOpenFileName( this, tr( "Load Color Map from File" ), lastDir, tr( "Textfile (*.txt)" ) );
447  QFile inputFile( fileName );
448  if ( inputFile.open( QFile::ReadOnly ) )
449  {
450  //clear the current tree
451  mColormapTreeWidget->clear();
452 
453  QTextStream inputStream( &inputFile );
454  QString inputLine;
455  QStringList inputStringComponents;
456  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
457 
458  //read through the input looking for valid data
459  while ( !inputStream.atEnd() )
460  {
461  lineCounter++;
462  inputLine = inputStream.readLine();
463  if ( !inputLine.isEmpty() )
464  {
465  if ( !inputLine.simplified().startsWith( '#' ) )
466  {
467  if ( inputLine.contains( QLatin1String( "INTERPOLATION" ), Qt::CaseInsensitive ) )
468  {
469  inputStringComponents = inputLine.split( ':' );
470  if ( inputStringComponents.size() == 2 )
471  {
472  if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "INTERPOLATED" ), Qt::CaseInsensitive ) == 0 )
473  {
474  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
475  }
476  else if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "DISCRETE" ), Qt::CaseInsensitive ) == 0 )
477  {
478  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Discrete ) );
479  }
480  else
481  {
482  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Exact ) );
483  }
484  }
485  else
486  {
487  importError = true;
488  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
489  }
490  }
491  else
492  {
493  inputStringComponents = inputLine.split( ',' );
494  if ( inputStringComponents.size() == 6 )
495  {
496  QgsColorRampShader::ColorRampItem currentItem( inputStringComponents[0].toDouble(),
497  QColor::fromRgb( inputStringComponents[1].toInt(), inputStringComponents[2].toInt(),
498  inputStringComponents[3].toInt(), inputStringComponents[4].toInt() ),
499  inputStringComponents[5] );
500  colorRampItems.push_back( currentItem );
501  }
502  else
503  {
504  importError = true;
505  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
506  }
507  }
508  }
509  }
510  lineCounter++;
511  }
512  populateColormapTreeWidget( colorRampItems );
513 
514  QFileInfo fileInfo( fileName );
515  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
516 
517  if ( importError )
518  {
519  QMessageBox::warning( this, tr( "Load Color Map from File" ), tr( "The following lines contained errors\n\n" ) + badLines );
520  }
521  }
522  else if ( !fileName.isEmpty() )
523  {
524  QMessageBox::warning( this, tr( "Load Color Map from File" ), tr( "Read access denied. Adjust the file permissions and try again.\n\n" ) );
525  }
526 
528  emit widgetChanged();
529 }
530 
531 void QgsColorRampShaderWidget::mExportToFileButton_clicked()
532 {
533  QgsSettings settings;
534  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
535  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Color Map as File" ), lastDir, tr( "Textfile (*.txt)" ) );
536  if ( !fileName.isEmpty() )
537  {
538  if ( !fileName.endsWith( QLatin1String( ".txt" ), Qt::CaseInsensitive ) )
539  {
540  fileName = fileName + ".txt";
541  }
542 
543  QFile outputFile( fileName );
544  if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
545  {
546  QTextStream outputStream( &outputFile );
547  outputStream << "# " << tr( "QGIS Generated Color Map Export File" ) << '\n';
548  outputStream << "INTERPOLATION:";
549  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
550  switch ( interpolation )
551  {
553  outputStream << "INTERPOLATED\n";
554  break;
556  outputStream << "DISCRETE\n";
557  break;
559  outputStream << "EXACT\n";
560  break;
561  }
562 
563  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
564  QTreeWidgetItem *currentItem = nullptr;
565  QColor color;
566  for ( int i = 0; i < topLevelItemCount; ++i )
567  {
568  currentItem = mColormapTreeWidget->topLevelItem( i );
569  if ( !currentItem )
570  {
571  continue;
572  }
573  color = currentItem->data( ColorColumn, Qt::EditRole ).value<QColor>();
574  outputStream << currentItem->text( ValueColumn ).toDouble() << ',';
575  outputStream << color.red() << ',' << color.green() << ',' << color.blue() << ',' << color.alpha() << ',';
576  if ( currentItem->text( LabelColumn ).isEmpty() )
577  {
578  outputStream << "Color entry " << i + 1 << '\n';
579  }
580  else
581  {
582  outputStream << currentItem->text( LabelColumn ) << '\n';
583  }
584  }
585  outputStream.flush();
586  outputFile.close();
587 
588  QFileInfo fileInfo( fileName );
589  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
590  }
591  else
592  {
593  QMessageBox::warning( this, tr( "Save Color Map as File" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
594  }
595  }
596 }
597 
598 void QgsColorRampShaderWidget::mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem *item, int column )
599 {
600  if ( !item )
601  {
602  return;
603  }
604 
605  if ( column == LabelColumn )
606  {
607  // Set text color to default black, which signifies a manually edited label
608  item->setForeground( LabelColumn, QBrush() );
609  }
610 }
611 
612 void QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited( QTreeWidgetItem *item, int column )
613 {
614  Q_UNUSED( item )
615 
616  switch ( column )
617  {
618  case ValueColumn:
619  {
620  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
621  autoLabel();
622 
624 
625  emit widgetChanged();
626  break;
627  }
628 
629  case LabelColumn:
630  {
631  // call autoLabel to fill when empty or gray out when same as autoLabel
632  autoLabel();
633  emit widgetChanged();
634  break;
635  }
636 
637  case ColorColumn:
638  {
640  emit widgetChanged();
641  break;
642  }
643  }
644 }
645 
647 {
648  if ( colorRampShader.sourceColorRamp() )
649  {
650  btnColorRamp->setColorRamp( colorRampShader.sourceColorRamp() );
651  }
652  else
653  {
654  QgsSettings settings;
655  QString defaultPalette = settings.value( QStringLiteral( "/Raster/defaultPalette" ), "Spectral" ).toString();
656  btnColorRamp->setColorRampFromName( defaultPalette );
657  }
658 
659  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( colorRampShader.colorRampType() ) );
660 
661  mColormapTreeWidget->clear();
662  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader.colorRampItemList();
663  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
664  for ( ; it != colorRampItemList.end(); ++it )
665  {
666  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
667  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
668  newItem->setData( ColorColumn, Qt::EditRole, it->color );
669  newItem->setText( LabelColumn, it->label );
670  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
671  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
672  this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited );
673  }
674  setUnitFromLabels();
675 
676  mClipCheckBox->setChecked( colorRampShader.clip() );
677  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( colorRampShader.classificationMode() ) );
678  mNumberOfEntriesSpinBox->setValue( colorRampShader.colorRampItemList().count() ); // some default
679 }
680 
681 void QgsColorRampShaderWidget::mColorInterpolationComboBox_currentIndexChanged( int index )
682 {
683  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->itemData( index ).toInt() );
684 
685  mClipCheckBox->setEnabled( interpolation == QgsColorRampShader::Interpolated );
686 
687  QString valueLabel;
688  QString valueToolTip;
689  switch ( interpolation )
690  {
692  valueLabel = tr( "Value" );
693  valueToolTip = tr( "Value for color stop" );
694  break;
696  valueLabel = tr( "Value <=" );
697  valueToolTip = tr( "Maximum value for class" );
698  break;
700  valueLabel = tr( "Value =" );
701  valueToolTip = tr( "Value for color" );
702  break;
703  }
704 
705  QTreeWidgetItem *header = mColormapTreeWidget->headerItem();
706  header->setText( ValueColumn, valueLabel );
707  header->setToolTip( ValueColumn, valueToolTip );
708 
709  autoLabel();
710  emit widgetChanged();
711 }
712 
714 {
715  if ( !qgsDoubleNear( mMin, min ) || !qgsDoubleNear( mMax, max ) )
716  {
717  setMinimumMaximum( min, max );
718  classify();
719  }
720 }
721 
722 void QgsColorRampShaderWidget::setMinimumMaximum( double min, double max )
723 {
724  mMin = min;
725  mMax = max;
726  resetClassifyButton();
727 }
728 
730 {
731  return mMin;
732 }
733 
735 {
736  return mMax;
737 }
738 
739 
740 
742 {
743  QTreeWidgetItem *item = mColormapTreeWidget->topLevelItem( 0 );
744  if ( !item )
745  {
746  return;
747  }
748 
749  double min = item->text( ValueColumn ).toDouble();
750  item = mColormapTreeWidget->topLevelItem( mColormapTreeWidget->topLevelItemCount() - 1 );
751  double max = item->text( ValueColumn ).toDouble();
752 
753  if ( !qgsDoubleNear( mMin, min ) || !qgsDoubleNear( mMax, max ) )
754  {
755  mMin = min;
756  mMax = max;
757  emit minimumMaximumChangedFromTree( min, max );
758  }
759 }
760 
761 void QgsColorRampShaderWidget::resetClassifyButton()
762 {
763  mClassifyButton->setEnabled( true );
764  if ( std::isnan( mMin ) || std::isnan( mMax ) || mMin >= mMax )
765  {
766  mClassifyButton->setEnabled( false );
767  }
768 }
769 
770 void QgsColorRampShaderWidget::changeColor()
771 {
772  QList<QTreeWidgetItem *> itemList;
773  itemList = mColormapTreeWidget->selectedItems();
774  if ( itemList.isEmpty() )
775  {
776  return;
777  }
778  QTreeWidgetItem *firstItem = itemList.first();
779 
780  QColor currentColor = firstItem->data( ColorColumn, Qt::EditRole ).value<QColor>();
781  QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast< QWidget * >( parent() ) );
782  if ( panel && panel->dockMode() )
783  {
785  colorWidget->setPanelTitle( tr( "Select Color" ) );
786  colorWidget->setAllowOpacity( true );
787  connect( colorWidget, &QgsCompoundColorWidget::currentColorChanged, this, [ = ]( const QColor & newColor )
788  {
789  for ( QTreeWidgetItem *item : qgis::as_const( itemList ) )
790  {
791  item->setData( ColorColumn, Qt::EditRole, newColor );
792  }
793 
795  emit widgetChanged();
796  } );
797  panel->openPanel( colorWidget );
798  }
799  else
800  {
801  // modal dialog version... yuck
802  QColor newColor = QgsColorDialog::getColor( currentColor, this, QStringLiteral( "Change Color" ), true );
803  if ( newColor.isValid() )
804  {
805  for ( QTreeWidgetItem *item : qgis::as_const( itemList ) )
806  {
807  item->setData( ColorColumn, Qt::EditRole, newColor );
808  }
809 
811  emit widgetChanged();
812  }
813  }
814 }
815 
816 void QgsColorRampShaderWidget::changeOpacity()
817 {
818  QList<QTreeWidgetItem *> itemList;
819  itemList = mColormapTreeWidget->selectedItems();
820  if ( itemList.isEmpty() )
821  {
822  return;
823  }
824  QTreeWidgetItem *firstItem = itemList.first();
825 
826  bool ok;
827  double oldOpacity = firstItem->data( ColorColumn, Qt::EditRole ).value<QColor>().alpha() / 255 * 100;
828  double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
829  if ( ok )
830  {
831  int newOpacity = static_cast<int>( opacity / 100 * 255 );
832  const auto constItemList = itemList;
833  for ( QTreeWidgetItem *item : constItemList )
834  {
835  QColor newColor = item->data( ColorColumn, Qt::EditRole ).value<QColor>();
836  newColor.setAlpha( newOpacity );
837  item->setData( ColorColumn, Qt::EditRole, newColor );
838  }
839 
841  emit widgetChanged();
842  }
843 }
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: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: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.
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.