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