QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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  Q_FOREACH ( QTreeWidgetItem *item, itemList )
289  {
290  delete item;
291  }
292 
294  emit widgetChanged();
295 }
296 
298 {
299  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
300  if ( !ramp || std::isnan( mMin ) || std::isnan( mMax ) )
301  {
302  return;
303  }
304 
305  std::unique_ptr< QgsColorRampShader > colorRampShader( new QgsColorRampShader(
306  mMin, mMax,
307  ramp.release(),
308  static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ),
309  static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ) )
310  );
311 
312  // only for Quantile we need band and provider and extent
313  colorRampShader->classifyColorRamp( mNumberOfEntriesSpinBox->value(),
314  mBand,
315  mExtent,
316  mRasterDataProvider );
317  colorRampShader->setClip( mClipCheckBox->isChecked() );
318 
319  mColormapTreeWidget->clear();
320 
321  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
322  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
323  for ( ; it != colorRampItemList.end(); ++it )
324  {
325  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
326  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
327  newItem->setData( ColorColumn, Qt::EditRole, it->color );
328  newItem->setText( LabelColumn, QString() ); // Labels will be populated in autoLabel()
329  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
330  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
331  this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited );
332  }
333  mClipCheckBox->setChecked( colorRampShader->clip() );
334 
335  autoLabel();
336  emit widgetChanged();
337 }
338 
339 void QgsColorRampShaderWidget::mClassificationModeComboBox_currentIndexChanged( int index )
340 {
341  QgsColorRampShader::ClassificationMode mode = static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->itemData( index ).toInt() );
342  mNumberOfEntriesSpinBox->setEnabled( mode != QgsColorRampShader::Continuous );
343  emit classificationModeChanged( mode );
344 
345 }
346 
347 void QgsColorRampShaderWidget::applyColorRamp()
348 {
349  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
350  if ( !ramp )
351  {
352  return;
353  }
354 
355  if ( !btnColorRamp->colorRampName().isEmpty() )
356  {
357  // Remember last used color ramp
358  QgsSettings settings;
359  settings.setValue( QStringLiteral( "Raster/defaultPalette" ), btnColorRamp->colorRampName() );
360  }
361 
362  bool enableContinuous = ( ramp->count() > 0 );
363  mClassificationModeComboBox->setEnabled( enableContinuous );
364  if ( !enableContinuous )
365  {
366  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::EqualInterval ) );
367  }
368 
369  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
370  if ( topLevelItemCount > 0 )
371  {
372  // if the list values has been customized, maintain pre-existing values
373  QTreeWidgetItem *currentItem = nullptr;
374  for ( int i = 0; i < topLevelItemCount; ++i )
375  {
376  currentItem = mColormapTreeWidget->topLevelItem( i );
377  if ( !currentItem )
378  {
379  continue;
380  }
381 
382  double value = currentItem->text( ValueColumn ).toDouble();
383  double position = ( value - mMin ) / ( mMax - mMin );
384  currentItem->setData( ColorColumn, Qt::EditRole, ramp->color( position ) );
385  }
386 
387  emit widgetChanged();
388  }
389  else
390  {
391  classify();
392  }
393 }
394 
395 void QgsColorRampShaderWidget::populateColormapTreeWidget( const QList<QgsColorRampShader::ColorRampItem> &colorRampItems )
396 {
397  mColormapTreeWidget->clear();
398  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItems.constBegin();
399  for ( ; it != colorRampItems.constEnd(); ++it )
400  {
401  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
402  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
403  newItem->setData( ColorColumn, Qt::EditRole, it->color );
404  newItem->setText( LabelColumn, it->label );
405  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
406  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
407  this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited );
408  }
409  setUnitFromLabels();
410  emit widgetChanged();
411 }
412 
413 void QgsColorRampShaderWidget::mLoadFromBandButton_clicked()
414 {
415  if ( !mRasterDataProvider )
416  return;
417 
418  QList<QgsColorRampShader::ColorRampItem> colorRampList = mRasterDataProvider->colorTable( mBand );
419  if ( !colorRampList.isEmpty() )
420  {
421  populateColormapTreeWidget( colorRampList );
422  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
423  }
424  else
425  {
426  QMessageBox::warning( this, tr( "Load Color Map" ), tr( "The color map for band %1 has no entries." ).arg( mBand ) );
427  }
429  emit widgetChanged();
430 }
431 
432 void QgsColorRampShaderWidget::mLoadFromFileButton_clicked()
433 {
434  int lineCounter = 0;
435  bool importError = false;
436  QString badLines;
437  QgsSettings settings;
438  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
439  QString fileName = QFileDialog::getOpenFileName( this, tr( "Load Color Map from File" ), lastDir, tr( "Textfile (*.txt)" ) );
440  QFile inputFile( fileName );
441  if ( inputFile.open( QFile::ReadOnly ) )
442  {
443  //clear the current tree
444  mColormapTreeWidget->clear();
445 
446  QTextStream inputStream( &inputFile );
447  QString inputLine;
448  QStringList inputStringComponents;
449  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
450 
451  //read through the input looking for valid data
452  while ( !inputStream.atEnd() )
453  {
454  lineCounter++;
455  inputLine = inputStream.readLine();
456  if ( !inputLine.isEmpty() )
457  {
458  if ( !inputLine.simplified().startsWith( '#' ) )
459  {
460  if ( inputLine.contains( QLatin1String( "INTERPOLATION" ), Qt::CaseInsensitive ) )
461  {
462  inputStringComponents = inputLine.split( ':' );
463  if ( inputStringComponents.size() == 2 )
464  {
465  if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "INTERPOLATED" ), Qt::CaseInsensitive ) == 0 )
466  {
467  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
468  }
469  else if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "DISCRETE" ), Qt::CaseInsensitive ) == 0 )
470  {
471  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Discrete ) );
472  }
473  else
474  {
475  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Exact ) );
476  }
477  }
478  else
479  {
480  importError = true;
481  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
482  }
483  }
484  else
485  {
486  inputStringComponents = inputLine.split( ',' );
487  if ( inputStringComponents.size() == 6 )
488  {
489  QgsColorRampShader::ColorRampItem currentItem( inputStringComponents[0].toDouble(),
490  QColor::fromRgb( inputStringComponents[1].toInt(), inputStringComponents[2].toInt(),
491  inputStringComponents[3].toInt(), inputStringComponents[4].toInt() ),
492  inputStringComponents[5] );
493  colorRampItems.push_back( currentItem );
494  }
495  else
496  {
497  importError = true;
498  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
499  }
500  }
501  }
502  }
503  lineCounter++;
504  }
505  populateColormapTreeWidget( colorRampItems );
506 
507  QFileInfo fileInfo( fileName );
508  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
509 
510  if ( importError )
511  {
512  QMessageBox::warning( this, tr( "Load Color Map from File" ), tr( "The following lines contained errors\n\n" ) + badLines );
513  }
514  }
515  else if ( !fileName.isEmpty() )
516  {
517  QMessageBox::warning( this, tr( "Load Color Map from File" ), tr( "Read access denied. Adjust the file permissions and try again.\n\n" ) );
518  }
519 
521  emit widgetChanged();
522 }
523 
524 void QgsColorRampShaderWidget::mExportToFileButton_clicked()
525 {
526  QgsSettings settings;
527  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
528  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Color Map as File" ), lastDir, tr( "Textfile (*.txt)" ) );
529  if ( !fileName.isEmpty() )
530  {
531  if ( !fileName.endsWith( QLatin1String( ".txt" ), Qt::CaseInsensitive ) )
532  {
533  fileName = fileName + ".txt";
534  }
535 
536  QFile outputFile( fileName );
537  if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
538  {
539  QTextStream outputStream( &outputFile );
540  outputStream << "# " << tr( "QGIS Generated Color Map Export File" ) << '\n';
541  outputStream << "INTERPOLATION:";
542  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
543  switch ( interpolation )
544  {
546  outputStream << "INTERPOLATED\n";
547  break;
549  outputStream << "DISCRETE\n";
550  break;
552  outputStream << "EXACT\n";
553  break;
554  }
555 
556  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
557  QTreeWidgetItem *currentItem = nullptr;
558  QColor color;
559  for ( int i = 0; i < topLevelItemCount; ++i )
560  {
561  currentItem = mColormapTreeWidget->topLevelItem( i );
562  if ( !currentItem )
563  {
564  continue;
565  }
566  color = currentItem->data( ColorColumn, Qt::EditRole ).value<QColor>();
567  outputStream << currentItem->text( ValueColumn ).toDouble() << ',';
568  outputStream << color.red() << ',' << color.green() << ',' << color.blue() << ',' << color.alpha() << ',';
569  if ( currentItem->text( LabelColumn ).isEmpty() )
570  {
571  outputStream << "Color entry " << i + 1 << '\n';
572  }
573  else
574  {
575  outputStream << currentItem->text( LabelColumn ) << '\n';
576  }
577  }
578  outputStream.flush();
579  outputFile.close();
580 
581  QFileInfo fileInfo( fileName );
582  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
583  }
584  else
585  {
586  QMessageBox::warning( this, tr( "Save Color Map as File" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
587  }
588  }
589 }
590 
591 void QgsColorRampShaderWidget::mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem *item, int column )
592 {
593  if ( !item )
594  {
595  return;
596  }
597 
598  if ( column == ColorColumn )
599  {
600  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
601  QColor newColor = QgsColorDialog::getColor( item->data( column, Qt::EditRole ).value<QColor>(), this, QStringLiteral( "Change Color" ), true );
602  if ( newColor.isValid() )
603  {
604  item->setData( ColorColumn, Qt::EditRole, newColor );
606  emit widgetChanged();
607  }
608  }
609  else
610  {
611  if ( column == LabelColumn )
612  {
613  // Set text color to default black, which signifies a manually edited label
614  item->setForeground( LabelColumn, QBrush() );
615  }
616  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
617  }
618 }
619 
620 void QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited( QTreeWidgetItem *item, int column )
621 {
622  Q_UNUSED( item );
623 
624  if ( column == ValueColumn )
625  {
626  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
627  autoLabel();
628 
630 
631  emit widgetChanged();
632  }
633  else if ( column == LabelColumn )
634  {
635  // call autoLabel to fill when empty or gray out when same as autoLabel
636  autoLabel();
637  emit widgetChanged();
638  }
639 }
640 
642 {
643  if ( colorRampShader.sourceColorRamp() )
644  {
645  btnColorRamp->setColorRamp( colorRampShader.sourceColorRamp() );
646  }
647  else
648  {
649  QgsSettings settings;
650  QString defaultPalette = settings.value( QStringLiteral( "/Raster/defaultPalette" ), "Spectral" ).toString();
651  btnColorRamp->setColorRampFromName( defaultPalette );
652  }
653 
654  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( colorRampShader.colorRampType() ) );
655 
656  mColormapTreeWidget->clear();
657  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader.colorRampItemList();
658  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
659  for ( ; it != colorRampItemList.end(); ++it )
660  {
661  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
662  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
663  newItem->setData( ColorColumn, Qt::EditRole, it->color );
664  newItem->setText( LabelColumn, it->label );
665  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
666  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
667  this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited );
668  }
669  setUnitFromLabels();
670 
671  mClipCheckBox->setChecked( colorRampShader.clip() );
672  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( colorRampShader.classificationMode() ) );
673  mNumberOfEntriesSpinBox->setValue( colorRampShader.colorRampItemList().count() ); // some default
674 }
675 
676 void QgsColorRampShaderWidget::mColorInterpolationComboBox_currentIndexChanged( int index )
677 {
678  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->itemData( index ).toInt() );
679 
680  mClipCheckBox->setEnabled( interpolation == QgsColorRampShader::Interpolated );
681 
682  QString valueLabel;
683  QString valueToolTip;
684  switch ( interpolation )
685  {
687  valueLabel = tr( "Value" );
688  valueToolTip = tr( "Value for color stop" );
689  break;
691  valueLabel = tr( "Value <=" );
692  valueToolTip = tr( "Maximum value for class" );
693  break;
695  valueLabel = tr( "Value =" );
696  valueToolTip = tr( "Value for color" );
697  break;
698  }
699 
700  QTreeWidgetItem *header = mColormapTreeWidget->headerItem();
701  header->setText( ValueColumn, valueLabel );
702  header->setToolTip( ValueColumn, valueToolTip );
703 
704  autoLabel();
705  emit widgetChanged();
706 }
707 
709 {
710  if ( !qgsDoubleNear( mMin, min ) || !qgsDoubleNear( mMax, max ) )
711  {
712  setMinimumMaximum( min, max );
713  classify();
714  }
715 }
716 
717 void QgsColorRampShaderWidget::setMinimumMaximum( double min, double max )
718 {
719  mMin = min;
720  mMax = max;
721  resetClassifyButton();
722 }
723 
725 {
726  return mMin;
727 }
728 
730 {
731  return mMax;
732 }
733 
734 
735 
737 {
738  QTreeWidgetItem *item = mColormapTreeWidget->topLevelItem( 0 );
739  if ( !item )
740  {
741  return;
742  }
743 
744  double min = item->text( ValueColumn ).toDouble();
745  item = mColormapTreeWidget->topLevelItem( mColormapTreeWidget->topLevelItemCount() - 1 );
746  double max = item->text( ValueColumn ).toDouble();
747 
748  if ( !qgsDoubleNear( mMin, min ) || !qgsDoubleNear( mMax, max ) )
749  {
750  mMin = min;
751  mMax = max;
752  emit minimumMaximumChangedFromTree( min, max );
753  }
754 }
755 
756 void QgsColorRampShaderWidget::resetClassifyButton()
757 {
758  mClassifyButton->setEnabled( true );
759  if ( std::isnan( mMin ) || std::isnan( mMax ) || mMin >= mMax )
760  {
761  mClassifyButton->setEnabled( false );
762  }
763 }
764 
765 void QgsColorRampShaderWidget::changeColor()
766 {
767  QList<QTreeWidgetItem *> itemList;
768  itemList = mColormapTreeWidget->selectedItems();
769  if ( itemList.isEmpty() )
770  {
771  return;
772  }
773  QTreeWidgetItem *firstItem = itemList.first();
774 
775  QColor newColor = QgsColorDialog::getColor( firstItem->data( ColorColumn, Qt::EditRole ).value<QColor>(), this, QStringLiteral( "Change Color" ), true );
776  if ( newColor.isValid() )
777  {
778  Q_FOREACH ( QTreeWidgetItem *item, itemList )
779  {
780  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
781  item->setData( ColorColumn, Qt::EditRole, newColor );
782  }
783 
785  emit widgetChanged();
786  }
787 }
788 
789 void QgsColorRampShaderWidget::changeOpacity()
790 {
791  QList<QTreeWidgetItem *> itemList;
792  itemList = mColormapTreeWidget->selectedItems();
793  if ( itemList.isEmpty() )
794  {
795  return;
796  }
797  QTreeWidgetItem *firstItem = itemList.first();
798 
799  bool ok;
800  double oldOpacity = firstItem->data( ColorColumn, Qt::EditRole ).value<QColor>().alpha() / 255 * 100;
801  double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
802  if ( ok )
803  {
804  int newOpacity = static_cast<int>( opacity / 100 * 255 );
805  Q_FOREACH ( QTreeWidgetItem *item, itemList )
806  {
807  QColor newColor = item->data( ColorColumn, Qt::EditRole ).value<QColor>();
808  newColor.setAlpha( newOpacity );
809  item->setData( ColorColumn, Qt::EditRole, newColor );
810  }
811 
813  emit widgetChanged();
814  }
815 }
ClassificationMode classificationMode() const
Returns the classification mode.
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:40
double maximum() const
Gets max value.
void classificationModeChanged(QgsColorRampShader::ClassificationMode mode)
Classification mode changed.
void setColorRampItemList(const QList< QgsColorRampShader::ColorRampItem > &list)
Sets a custom colormap.
QgsColorRampShader shader() const
Returns shared function used in the renderer.
Uses quantile (i.e. equal pixel) count.
QgsColorRamp * sourceColorRamp() const
Returns the source color ramp.
void initializeForUseWithRasterLayer()
Allows quantile classification mode for raster layers.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:152
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.
void colorRampChanged()
Emitted whenever a new color ramp is set for the button.
void setMinimumMaximum(double minimum, double maximum)
Sets min max.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:278
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
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.
double minimum() const
Gets min value.
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.
Type
Supported methods for color interpolation.
void setRasterDataProvider(QgsRasterDataProvider *dp)
Associates raster with the widget, only when used for raster layer.
QList< QgsColorRampShader::ColorRampItem > colorRampItemList() const
Returns the custom colormap.
void setColorRampType(QgsColorRampShader::Type colorRampType)
Sets the color ramp type.
void populateColormapTreeWidget(const QList< QgsColorRampShader::ColorRampItem > &colorRampItems)
Populates color ramp tree from ramp items.
bool clip() const
Returns whether the shader will clip values which are out of range.
void setMinimumMaximumAndClassify(double minimum, double maximum)
Sets min max and classify color tree.
void setSourceColorRamp(QgsColorRamp *colorramp)
Set the source color ramp.
Type colorRampType() const
Returns the color ramp type.
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.
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.
void itemEdited(QTreeWidgetItem *item, int column)
This signal is emitted when the contents of the column in the specified item has been edited by the u...
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.
virtual QList< QgsColorRampShader::ColorRampItem > colorTable(int bandNo) const
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.