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