QGIS API Documentation  2.99.0-Master (d55fa22)
qgssinglebandpseudocolorrendererwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssinglebandpseudocolorrendererwidget.cpp
3  ------------------------------------------
4  begin : February 2012
5  copyright : (C) 2012 by Marco Hugentobler
6  email : marco at sourcepole dot ch
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 
20 #include "qgsrasterlayer.h"
21 #include "qgsrasterdataprovider.h"
22 #include "qgsrastershader.h"
23 #include "qgsrasterminmaxwidget.h"
24 #include "qgstreewidgetitem.h"
25 #include "qgssettings.h"
26 
27 // for color ramps - todo add rasterStyle and refactor raster vs. vector ramps
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  : QgsRasterRendererWidget( layer, extent )
44  , mMinMaxWidget( nullptr )
45  , mDisableMinMaxWidgetRefresh( false )
46  , mMinMaxOrigin( 0 )
47 {
48  QgsSettings settings;
49 
50  setupUi( this );
51 
52  contextMenu = new QMenu( tr( "Options" ), this );
53  contextMenu->addAction( tr( "Change color" ), this, SLOT( changeColor() ) );
54  contextMenu->addAction( tr( "Change opacity" ), this, SLOT( changeOpacity() ) );
55 
56  mColormapTreeWidget->setColumnWidth( ColorColumn, 50 );
57  mColormapTreeWidget->setContextMenuPolicy( Qt::CustomContextMenu );
58  mColormapTreeWidget->setSelectionMode( QAbstractItemView::ExtendedSelection );
59  connect( mColormapTreeWidget, &QTreeView::customContextMenuRequested, [ = ]( const QPoint & ) { contextMenu->exec( QCursor::pos() ); }
60  );
61 
62  QString defaultPalette = settings.value( QStringLiteral( "Raster/defaultPalette" ), "" ).toString();
63  btnColorRamp->setColorRampFromName( defaultPalette );
64 
65  if ( !mRasterLayer )
66  {
67  return;
68  }
69 
71  if ( !provider )
72  {
73  return;
74  }
75 
76  // Must be before adding items to mBandComboBox (signal)
77  mMinLineEdit->setValidator( new QDoubleValidator( mMinLineEdit ) );
78  mMaxLineEdit->setValidator( new QDoubleValidator( mMaxLineEdit ) );
79 
80  mMinMaxWidget = new QgsRasterMinMaxWidget( layer, this );
81  mMinMaxWidget->setExtent( extent );
82  mMinMaxWidget->setMapCanvas( mCanvas );
83 
84  QHBoxLayout *layout = new QHBoxLayout();
85  layout->setContentsMargins( 0, 0, 0, 0 );
86  mMinMaxContainerWidget->setLayout( layout );
87  layout->addWidget( mMinMaxWidget );
88 
91 
92  mBandComboBox->setLayer( mRasterLayer );
93 
94  mColorInterpolationComboBox->addItem( tr( "Discrete" ), QgsColorRampShader::Discrete );
95  mColorInterpolationComboBox->addItem( tr( "Linear" ), QgsColorRampShader::Interpolated );
96  mColorInterpolationComboBox->addItem( tr( "Exact" ), QgsColorRampShader::Exact );
97  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
98 
99  mClassificationModeComboBox->addItem( tr( "Continuous" ), QgsColorRampShader::Continuous );
100  mClassificationModeComboBox->addItem( tr( "Equal interval" ), QgsColorRampShader::EqualInterval );
101  mClassificationModeComboBox->addItem( tr( "Quantile" ), QgsColorRampShader::Quantile );
102  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::Continuous ) );
103 
104  mNumberOfEntriesSpinBox->setValue( 5 ); // some default
105 
106  setFromRenderer( layer->renderer() );
107 
108  // If there is currently no min/max, load default with user current default options
109  if ( mMinLineEdit->text().isEmpty() || mMaxLineEdit->text().isEmpty() )
110  {
111  QgsRasterMinMaxOrigin minMaxOrigin = mMinMaxWidget->minMaxOrigin();
112  if ( minMaxOrigin.limits() == QgsRasterMinMaxOrigin::None )
113  {
115  mMinMaxWidget->setFromMinMaxOrigin( minMaxOrigin );
116  }
117  mMinMaxWidget->doComputations();
118  }
119 
120  on_mClassificationModeComboBox_currentIndexChanged( 0 );
121 
122  resetClassifyButton();
123 
124  connect( mClassificationModeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsSingleBandPseudoColorRendererWidget::classify );
125  connect( mClassifyButton, &QPushButton::clicked, this, &QgsSingleBandPseudoColorRendererWidget::applyColorRamp );
126  connect( btnColorRamp, &QgsColorRampButton::colorRampChanged, this, &QgsSingleBandPseudoColorRendererWidget::applyColorRamp );
127  connect( mNumberOfEntriesSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsSingleBandPseudoColorRendererWidget::classify );
129  connect( mBandComboBox, &QgsRasterBandComboBox::bandChanged, this, &QgsSingleBandPseudoColorRendererWidget::bandChanged );
130  connect( mClipCheckBox, &QAbstractButton::toggled, this, &QgsRasterRendererWidget::widgetChanged );
131 }
132 
134 {
135  QgsRasterShader *rasterShader = new QgsRasterShader();
136  QgsColorRampShader *colorRampShader = new QgsColorRampShader( lineEditValue( mMinLineEdit ), lineEditValue( mMaxLineEdit ) );
137  colorRampShader->setColorRampType( static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ) );
138  colorRampShader->setClassificationMode( static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ) );
139  colorRampShader->setClip( mClipCheckBox->isChecked() );
140 
141  //iterate through mColormapTreeWidget and set colormap info of layer
142  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
143  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
144  QTreeWidgetItem *currentItem = nullptr;
145  for ( int i = 0; i < topLevelItemCount; ++i )
146  {
147  currentItem = mColormapTreeWidget->topLevelItem( i );
148  if ( !currentItem )
149  {
150  continue;
151  }
152  QgsColorRampShader::ColorRampItem newColorRampItem;
153  newColorRampItem.value = currentItem->text( ValueColumn ).toDouble();
154  newColorRampItem.color = currentItem->background( ColorColumn ).color();
155  newColorRampItem.label = currentItem->text( LabelColumn );
156  colorRampItems.append( newColorRampItem );
157  }
158  // sort the shader items
159  std::sort( colorRampItems.begin(), colorRampItems.end() );
160  colorRampShader->setColorRampItemList( colorRampItems );
161 
162  if ( !btnColorRamp->isNull() )
163  {
164  colorRampShader->setSourceColorRamp( btnColorRamp->colorRamp() );
165  }
166 
167  rasterShader->setRasterShaderFunction( colorRampShader );
168 
169  int bandNumber = mBandComboBox->currentBand();
171  renderer->setClassificationMin( lineEditValue( mMinLineEdit ) );
172  renderer->setClassificationMax( lineEditValue( mMaxLineEdit ) );
173  renderer->setMinMaxOrigin( mMinMaxWidget->minMaxOrigin() );
174  return renderer;
175 }
176 
178 {
179  mMinMaxWidget->doComputations();
180  if ( mColormapTreeWidget->topLevelItemCount() == 0 )
181  applyColorRamp();
182 }
183 
185 {
187  mMinMaxWidget->setMapCanvas( canvas );
188 }
189 
190 void QgsSingleBandPseudoColorRendererWidget::autoLabel()
191 {
192  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
193  bool discrete = interpolation == QgsColorRampShader::Discrete;
194  QString unit = mUnitLineEdit->text();
195  QString label;
196  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
197  QTreeWidgetItem *currentItem = nullptr;
198  for ( int i = 0; i < topLevelItemCount; ++i )
199  {
200  currentItem = mColormapTreeWidget->topLevelItem( i );
201  //If the item is null or does not have a pixel values set, skip
202  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
203  {
204  continue;
205  }
206 
207  if ( discrete )
208  {
209  if ( i == 0 )
210  {
211  label = "<= " + currentItem->text( ValueColumn ) + unit;
212  }
213  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
214  {
215  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + unit;
216  }
217  else
218  {
219  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn ) + unit;
220  }
221  }
222  else
223  {
224  label = currentItem->text( ValueColumn ) + unit;
225  }
226 
227  if ( currentItem->text( LabelColumn ).isEmpty() || currentItem->text( LabelColumn ) == label || currentItem->foreground( LabelColumn ).color() == QColor( Qt::gray ) )
228  {
229  currentItem->setText( LabelColumn, label );
230  currentItem->setForeground( LabelColumn, QBrush( QColor( Qt::gray ) ) );
231  }
232  }
233 }
234 
235 void QgsSingleBandPseudoColorRendererWidget::setUnitFromLabels()
236 {
237  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
238  bool discrete = interpolation == QgsColorRampShader::Discrete;
239  QStringList allSuffixes;
240  QString label;
241  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
242  QTreeWidgetItem *currentItem = nullptr;
243  for ( int i = 0; i < topLevelItemCount; ++i )
244  {
245  currentItem = mColormapTreeWidget->topLevelItem( i );
246  //If the item is null or does not have a pixel values set, skip
247  if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
248  {
249  continue;
250  }
251 
252  if ( discrete )
253  {
254  if ( i == 0 )
255  {
256  label = "<= " + currentItem->text( ValueColumn );
257  }
258  else if ( currentItem->text( ValueColumn ).toDouble() == std::numeric_limits<double>::infinity() )
259  {
260  label = "> " + mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn );
261  }
262  else
263  {
264  label = mColormapTreeWidget->topLevelItem( i - 1 )->text( ValueColumn ) + " - " + currentItem->text( ValueColumn );
265  }
266  }
267  else
268  {
269  label = currentItem->text( ValueColumn );
270  }
271 
272  if ( currentItem->text( LabelColumn ).startsWith( label ) )
273  {
274  allSuffixes.append( currentItem->text( LabelColumn ).mid( label.length() ) );
275  }
276  }
277  // find most common suffix
278  QStringList suffixes = QStringList( allSuffixes );
279  suffixes.removeDuplicates();
280  int max = 0;
281  QString unit;
282  for ( int i = 0; i < suffixes.count(); ++i )
283  {
284  int n = allSuffixes.count( suffixes[i] );
285  if ( n > max )
286  {
287  max = n;
288  unit = suffixes[i];
289  }
290  }
291  // Set this suffix as unit if at least used twice
292  if ( max >= 2 )
293  {
294  mUnitLineEdit->setText( unit );
295  }
296  autoLabel();
297 }
298 
299 void QgsSingleBandPseudoColorRendererWidget::on_mAddEntryButton_clicked()
300 {
301  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
302  newItem->setText( ValueColumn, QStringLiteral( "0" ) );
303  newItem->setBackground( ColorColumn, QBrush( QColor( Qt::magenta ) ) );
304  newItem->setText( LabelColumn, QString() );
305  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
306  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
307  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
308  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
309  autoLabel();
310  emit widgetChanged();
311 }
312 
313 void QgsSingleBandPseudoColorRendererWidget::on_mDeleteEntryButton_clicked()
314 {
315  QList<QTreeWidgetItem *> itemList;
316  itemList = mColormapTreeWidget->selectedItems();
317  if ( itemList.isEmpty() )
318  {
319  return;
320  }
321 
322  Q_FOREACH ( QTreeWidgetItem *item, itemList )
323  {
324  delete item;
325  }
326  emit widgetChanged();
327 }
328 
330 {
331  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
332  if ( !ramp || qIsNaN( lineEditValue( mMinLineEdit ) ) || qIsNaN( lineEditValue( mMaxLineEdit ) ) )
333  {
334  return;
335  }
336 
337  QgsSingleBandPseudoColorRenderer *pr = new QgsSingleBandPseudoColorRenderer( mRasterLayer->dataProvider(), mBandComboBox->currentBand(), nullptr );
338  pr->setClassificationMin( lineEditValue( mMinLineEdit ) );
339  pr->setClassificationMax( lineEditValue( mMaxLineEdit ) );
340  pr->createShader( ramp.get(), static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() ), static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->currentData().toInt() ), mNumberOfEntriesSpinBox->value(), mClipCheckBox->isChecked(), minMaxWidget()->extent() );
341 
342  const QgsRasterShader *rasterShader = pr->shader();
343  if ( rasterShader )
344  {
345  const QgsColorRampShader *colorRampShader = dynamic_cast<const QgsColorRampShader *>( rasterShader->rasterShaderFunction() );
346  if ( colorRampShader )
347  {
348  mColormapTreeWidget->clear();
349 
350  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
351  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
352  for ( ; it != colorRampItemList.end(); ++it )
353  {
354  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
355  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
356  newItem->setBackground( ColorColumn, QBrush( it->color ) );
357  newItem->setText( LabelColumn, it->label );
358  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
359  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
360  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
361  }
362  mClipCheckBox->setChecked( colorRampShader->clip() );
363  }
364  }
365 
366  autoLabel();
367  emit widgetChanged();
368 }
369 
370 void QgsSingleBandPseudoColorRendererWidget::on_mClassificationModeComboBox_currentIndexChanged( int index )
371 {
372  QgsColorRampShader::ClassificationMode mode = static_cast< QgsColorRampShader::ClassificationMode >( mClassificationModeComboBox->itemData( index ).toInt() );
373  mNumberOfEntriesSpinBox->setEnabled( mode != QgsColorRampShader::Continuous );
374  mMinLineEdit->setEnabled( mode != QgsColorRampShader::Quantile );
375  mMaxLineEdit->setEnabled( mode != QgsColorRampShader::Quantile );
376 }
377 
378 void QgsSingleBandPseudoColorRendererWidget::applyColorRamp()
379 {
380  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
381  if ( !ramp )
382  {
383  return;
384  }
385 
386  if ( !btnColorRamp->colorRampName().isEmpty() )
387  {
388  // Remember last used color ramp
389  QgsSettings settings;
390  settings.setValue( QStringLiteral( "Raster/defaultPalette" ), btnColorRamp->colorRampName() );
391  }
392 
393  bool enableContinuous = ( ramp->count() > 0 );
394  mClassificationModeComboBox->setEnabled( enableContinuous );
395  if ( !enableContinuous )
396  {
397  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QgsColorRampShader::EqualInterval ) );
398  }
399 
400  classify();
401 }
402 
403 void QgsSingleBandPseudoColorRendererWidget::populateColormapTreeWidget( const QList<QgsColorRampShader::ColorRampItem> &colorRampItems )
404 {
405  mColormapTreeWidget->clear();
406  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItems.constBegin();
407  for ( ; it != colorRampItems.constEnd(); ++it )
408  {
409  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
410  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
411  newItem->setBackground( ColorColumn, QBrush( it->color ) );
412  newItem->setText( LabelColumn, it->label );
413  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
414  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
415  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
416  }
417  setUnitFromLabels();
418 }
419 
420 void QgsSingleBandPseudoColorRendererWidget::on_mLoadFromBandButton_clicked()
421 {
422  if ( !mRasterLayer || !mRasterLayer->dataProvider() )
423  {
424  return;
425  }
426 
427  int bandIndex = mBandComboBox->currentBand();
428 
429 
430  QList<QgsColorRampShader::ColorRampItem> colorRampList = mRasterLayer->dataProvider()->colorTable( bandIndex );
431  if ( !colorRampList.isEmpty() )
432  {
433  populateColormapTreeWidget( colorRampList );
434  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
435  }
436  else
437  {
438  QMessageBox::warning( this, tr( "Load Color Map" ), tr( "The color map for band %1 has no entries" ).arg( bandIndex ) );
439  }
440  emit widgetChanged();
441 }
442 
443 void QgsSingleBandPseudoColorRendererWidget::on_mLoadFromFileButton_clicked()
444 {
445  int lineCounter = 0;
446  bool importError = false;
447  QString badLines;
448  QgsSettings settings;
449  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
450  QString fileName = QFileDialog::getOpenFileName( this, tr( "Open file" ), lastDir, tr( "Textfile (*.txt)" ) );
451  QFile inputFile( fileName );
452  if ( inputFile.open( QFile::ReadOnly ) )
453  {
454  //clear the current tree
455  mColormapTreeWidget->clear();
456 
457  QTextStream inputStream( &inputFile );
458  QString inputLine;
459  QStringList inputStringComponents;
460  QList<QgsColorRampShader::ColorRampItem> colorRampItems;
461 
462  //read through the input looking for valid data
463  while ( !inputStream.atEnd() )
464  {
465  lineCounter++;
466  inputLine = inputStream.readLine();
467  if ( !inputLine.isEmpty() )
468  {
469  if ( !inputLine.simplified().startsWith( '#' ) )
470  {
471  if ( inputLine.contains( QLatin1String( "INTERPOLATION" ), Qt::CaseInsensitive ) )
472  {
473  inputStringComponents = inputLine.split( ':' );
474  if ( inputStringComponents.size() == 2 )
475  {
476  if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "INTERPOLATED" ), Qt::CaseInsensitive ) == 0 )
477  {
478  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Interpolated ) );
479  }
480  else if ( inputStringComponents[1].trimmed().toUpper().compare( QLatin1String( "DISCRETE" ), Qt::CaseInsensitive ) == 0 )
481  {
482  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Discrete ) );
483  }
484  else
485  {
486  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QgsColorRampShader::Exact ) );
487  }
488  }
489  else
490  {
491  importError = true;
492  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
493  }
494  }
495  else
496  {
497  inputStringComponents = inputLine.split( ',' );
498  if ( inputStringComponents.size() == 6 )
499  {
500  QgsColorRampShader::ColorRampItem currentItem( inputStringComponents[0].toDouble(),
501  QColor::fromRgb( inputStringComponents[1].toInt(), inputStringComponents[2].toInt(),
502  inputStringComponents[3].toInt(), inputStringComponents[4].toInt() ),
503  inputStringComponents[5] );
504  colorRampItems.push_back( currentItem );
505  }
506  else
507  {
508  importError = true;
509  badLines = badLines + QString::number( lineCounter ) + ":\t[" + inputLine + "]\n";
510  }
511  }
512  }
513  }
514  lineCounter++;
515  }
516  populateColormapTreeWidget( colorRampItems );
517 
518  QFileInfo fileInfo( fileName );
519  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
520 
521  if ( importError )
522  {
523  QMessageBox::warning( this, tr( "Import Error" ), tr( "The following lines contained errors\n\n" ) + badLines );
524  }
525  }
526  else if ( !fileName.isEmpty() )
527  {
528  QMessageBox::warning( this, tr( "Read access denied" ), tr( "Read access denied. Adjust the file permissions and try again.\n\n" ) );
529  }
530  emit widgetChanged();
531 }
532 
533 void QgsSingleBandPseudoColorRendererWidget::on_mExportToFileButton_clicked()
534 {
535  QgsSettings settings;
536  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
537  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save file" ), lastDir, tr( "Textfile (*.txt)" ) );
538  if ( !fileName.isEmpty() )
539  {
540  if ( !fileName.endsWith( QLatin1String( ".txt" ), Qt::CaseInsensitive ) )
541  {
542  fileName = fileName + ".txt";
543  }
544 
545  QFile outputFile( fileName );
546  if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
547  {
548  QTextStream outputStream( &outputFile );
549  outputStream << "# " << tr( "QGIS Generated Color Map Export File" ) << '\n';
550  outputStream << "INTERPOLATION:";
551  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->currentData().toInt() );
552  switch ( interpolation )
553  {
555  outputStream << "INTERPOLATED\n";
556  break;
558  outputStream << "DISCRETE\n";
559  break;
561  outputStream << "EXACT\n";
562  break;
563  }
564 
565  int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
566  QTreeWidgetItem *currentItem = nullptr;
567  QColor color;
568  for ( int i = 0; i < topLevelItemCount; ++i )
569  {
570  currentItem = mColormapTreeWidget->topLevelItem( i );
571  if ( !currentItem )
572  {
573  continue;
574  }
575  color = currentItem->background( ColorColumn ).color();
576  outputStream << currentItem->text( ValueColumn ).toDouble() << ',';
577  outputStream << color.red() << ',' << color.green() << ',' << color.blue() << ',' << color.alpha() << ',';
578  if ( currentItem->text( LabelColumn ).isEmpty() )
579  {
580  outputStream << "Color entry " << i + 1 << '\n';
581  }
582  else
583  {
584  outputStream << currentItem->text( LabelColumn ) << '\n';
585  }
586  }
587  outputStream.flush();
588  outputFile.close();
589 
590  QFileInfo fileInfo( fileName );
591  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
592  }
593  else
594  {
595  QMessageBox::warning( this, tr( "Write access denied" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
596  }
597  }
598 }
599 
600 void QgsSingleBandPseudoColorRendererWidget::on_mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem *item, int column )
601 {
602  if ( !item )
603  {
604  return;
605  }
606 
607  if ( column == ColorColumn )
608  {
609  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
610  QColor newColor = QgsColorDialog::getColor( item->background( column ).color(), this, QStringLiteral( "Change color" ), true );
611  if ( newColor.isValid() )
612  {
613  item->setBackground( ColorColumn, QBrush( newColor ) );
614  emit widgetChanged();
615  }
616  }
617  else
618  {
619  if ( column == LabelColumn )
620  {
621  // Set text color to default black, which signifies a manually edited label
622  item->setForeground( LabelColumn, QBrush() );
623  }
624  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
625  }
626 }
627 
628 void QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited( QTreeWidgetItem *item, int column )
629 {
630  Q_UNUSED( item );
631 
632  if ( column == ValueColumn )
633  {
634  mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
635  autoLabel();
636  emit widgetChanged();
637  }
638  else if ( column == LabelColumn )
639  {
640  // call autoLabel to fill when empty or gray out when same as autoLabel
641  autoLabel();
642  emit widgetChanged();
643  }
644 }
645 
647 {
648  const QgsSingleBandPseudoColorRenderer *pr = dynamic_cast<const QgsSingleBandPseudoColorRenderer *>( r );
649  if ( pr )
650  {
651  mBandComboBox->setBand( pr->band() );
652  mMinMaxWidget->setBands( QList< int >() << pr->band() );
653 
654  const QgsRasterShader *rasterShader = pr->shader();
655  if ( rasterShader )
656  {
657  const QgsColorRampShader *colorRampShader = dynamic_cast<const QgsColorRampShader *>( rasterShader->rasterShaderFunction() );
658  if ( colorRampShader )
659  {
660  if ( colorRampShader->sourceColorRamp() )
661  {
662  btnColorRamp->setColorRamp( colorRampShader->sourceColorRamp() );
663  }
664  else
665  {
666  QgsSettings settings;
667  QString defaultPalette = settings.value( "/Raster/defaultPalette", "Spectral" ).toString();
668  btnColorRamp->setColorRampFromName( defaultPalette );
669  }
670 
671  mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( colorRampShader->colorRampType() ) );
672 
673  const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
674  QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
675  for ( ; it != colorRampItemList.end(); ++it )
676  {
677  QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
678  newItem->setText( ValueColumn, QString::number( it->value, 'g', 15 ) );
679  newItem->setBackground( ColorColumn, QBrush( it->color ) );
680  newItem->setText( LabelColumn, it->label );
681  newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
682  connect( newItem, &QgsTreeWidgetItemObject::itemEdited,
683  this, &QgsSingleBandPseudoColorRendererWidget::mColormapTreeWidget_itemEdited );
684  }
685  setUnitFromLabels();
686  mClipCheckBox->setChecked( colorRampShader->clip() );
687 
688  mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( colorRampShader->classificationMode() ) );
689  mNumberOfEntriesSpinBox->setValue( colorRampShader->colorRampItemList().count() ); // some default
690  }
691  }
692  setLineEditValue( mMinLineEdit, pr->classificationMin() );
693  setLineEditValue( mMaxLineEdit, pr->classificationMax() );
694 
695  mMinMaxWidget->setFromMinMaxOrigin( pr->minMaxOrigin() );
696  }
697  else
698  {
699  mMinMaxWidget->setBands( QList< int >() << mBandComboBox->currentBand() );
700  }
701 }
702 
703 void QgsSingleBandPseudoColorRendererWidget::bandChanged()
704 {
705  QList<int> bands;
706  bands.append( mBandComboBox->currentBand() );
707  mMinMaxWidget->setBands( bands );
708 }
709 
710 void QgsSingleBandPseudoColorRendererWidget::on_mColorInterpolationComboBox_currentIndexChanged( int index )
711 {
712  QgsColorRampShader::Type interpolation = static_cast< QgsColorRampShader::Type >( mColorInterpolationComboBox->itemData( index ).toInt() );
713 
714  mClipCheckBox->setEnabled( interpolation == QgsColorRampShader::Interpolated );
715 
716  QString valueLabel;
717  QString valueToolTip;
718  switch ( interpolation )
719  {
721  valueLabel = tr( "Value" );
722  valueToolTip = tr( "Value for color stop" );
723  break;
725  valueLabel = tr( "Value <=" );
726  valueToolTip = tr( "Maximum value for class" );
727  break;
729  valueLabel = tr( "Value =" );
730  valueToolTip = tr( "Value for color" );
731  break;
732  }
733 
734  QTreeWidgetItem *header = mColormapTreeWidget->headerItem();
735  header->setText( ValueColumn, valueLabel );
736  header->setToolTip( ValueColumn, valueToolTip );
737 
738  autoLabel();
739  emit widgetChanged();
740 }
741 
743 {
744  Q_UNUSED( bandNo );
745  QgsDebugMsg( QString( "theBandNo = %1 min = %2 max = %3" ).arg( bandNo ).arg( min ).arg( max ) );
746 
747  mDisableMinMaxWidgetRefresh = true;
748  if ( qIsNaN( min ) )
749  {
750  mMinLineEdit->clear();
751  }
752  else
753  {
754  mMinLineEdit->setText( QString::number( min ) );
755  }
756 
757  if ( qIsNaN( max ) )
758  {
759  mMaxLineEdit->clear();
760  }
761  else
762  {
763  mMaxLineEdit->setText( QString::number( max ) );
764  }
765  mDisableMinMaxWidgetRefresh = false;
766  classify();
767 }
768 
769 void QgsSingleBandPseudoColorRendererWidget::setLineEditValue( QLineEdit *lineEdit, double value )
770 {
771  QString s;
772  if ( !qIsNaN( value ) )
773  {
774  s = QString::number( value );
775  }
776  lineEdit->setText( s );
777 }
778 
779 double QgsSingleBandPseudoColorRendererWidget::lineEditValue( const QLineEdit *lineEdit ) const
780 {
781  if ( lineEdit->text().isEmpty() )
782  {
783  return std::numeric_limits<double>::quiet_NaN();
784  }
785 
786  return lineEdit->text().toDouble();
787 }
788 
789 void QgsSingleBandPseudoColorRendererWidget::resetClassifyButton()
790 {
791  mClassifyButton->setEnabled( true );
792  double min = lineEditValue( mMinLineEdit );
793  double max = lineEditValue( mMaxLineEdit );
794  if ( qIsNaN( min ) || qIsNaN( max ) || min >= max )
795  {
796  mClassifyButton->setEnabled( false );
797  }
798 }
799 
800 void QgsSingleBandPseudoColorRendererWidget::changeColor()
801 {
802  QList<QTreeWidgetItem *> itemList;
803  itemList = mColormapTreeWidget->selectedItems();
804  if ( itemList.isEmpty() )
805  {
806  return;
807  }
808  QTreeWidgetItem *firstItem = itemList.first();
809 
810  QColor newColor = QgsColorDialog::getColor( firstItem->background( ColorColumn ).color(), this, QStringLiteral( "Change color" ), true );
811  if ( newColor.isValid() )
812  {
813  Q_FOREACH ( QTreeWidgetItem *item, itemList )
814  {
815  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
816  item->setBackground( ColorColumn, QBrush( newColor ) );
817  }
818  emit widgetChanged();
819  }
820 }
821 
822 void QgsSingleBandPseudoColorRendererWidget::changeOpacity()
823 {
824  QList<QTreeWidgetItem *> itemList;
825  itemList = mColormapTreeWidget->selectedItems();
826  if ( itemList.isEmpty() )
827  {
828  return;
829  }
830  QTreeWidgetItem *firstItem = itemList.first();
831 
832  bool ok;
833  double oldOpacity = firstItem->background( ColorColumn ).color().alpha() / 255 * 100;
834  double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
835  if ( ok )
836  {
837  int newOpacity = opacity / 100 * 255;
838  Q_FOREACH ( QTreeWidgetItem *item, itemList )
839  {
840  QColor newColor = item->background( ColorColumn ).color();
841  newColor.setAlpha( newOpacity );
842  item->setBackground( ColorColumn, QBrush( newColor ) );
843  }
844  emit widgetChanged();
845  }
846 }
847 
848 void QgsSingleBandPseudoColorRendererWidget::on_mMinLineEdit_textEdited( const QString & )
849 {
850  minMaxModified();
851  classify();
852 }
853 
854 void QgsSingleBandPseudoColorRendererWidget::on_mMaxLineEdit_textEdited( const QString & )
855 {
856  minMaxModified();
857  classify();
858 }
859 
860 void QgsSingleBandPseudoColorRendererWidget::minMaxModified()
861 {
862  if ( !mDisableMinMaxWidgetRefresh )
863  {
864  mMinMaxWidget->userHasSetManualMinMaxValues();
865  }
866 }
void setExtent(const QgsRectangle &extent)
Sets the extent to use for minimum and maximum value calculation.
A rectangle specified with double values.
Definition: qgsrectangle.h:38
Interface for all raster shaders.
void setColorRampItemList(const QList< QgsColorRampShader::ColorRampItem > &list)
Set custom colormap.
Uses quantile (i.e. equal pixel) count.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:54
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.
#define QgsDebugMsg(str)
Definition: qgslogger.h:37
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
QList< QgsColorRampShader::ColorRampItem > colorRampItemList() const
Get the custom colormap.
QgsRasterMinMaxOrigin minMaxOrigin()
Return a QgsRasterMinMaxOrigin object with the widget values.
const QgsRasterMinMaxOrigin & minMaxOrigin() const
Returns const reference to origin of min/max values.
QgsRasterRenderer * renderer() const
void setClip(bool clip)
Sets whether the shader should not render values out of range.
QgsRasterShader * shader()
Returns the raster shader.
void setLimits(Limits limits)
Set limits.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:73
Type
Supported methods for color interpolation.
virtual void setMapCanvas(QgsMapCanvas *canvas)
Sets the map canvas associated with the widget.
QgsRasterMinMaxWidget * minMaxWidget() override
Return min/max widget when it exists.
void setMapCanvas(QgsMapCanvas *canvas) override
Sets the map canvas associated with the widget.
void widgetChanged()
Emitted when something on the widget has changed.
void setColorRampType(QgsColorRampShader::Type colorRampType)
Set the color ramp type.
QgsRasterDataProvider * dataProvider() override
void setValue(const QString &key, const QVariant &value, const QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
This class describes the origin of min/max values.
virtual QString min(int index=0)
void bandChanged(int band)
This signal is emitted when the currently selected band changes.
QgsRasterShaderFunction * rasterShaderFunction()
void setBands(const QList< int > &bands)
Raster renderer pipe for single band pseudocolor.
void doComputations() override
Load programmatically with current values.
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), const bool allowAlpha=false)
Return a color selection from a color dialog.
void setSourceColorRamp(QgsColorRamp *colorramp)
Set the source color ramp.
void setRasterShaderFunction(QgsRasterShaderFunction *)
A public method that allows the user to set their own shader function.
Custom QgsTreeWidgetItem with extra signals when item is edited.
void setMinMaxOrigin(const QgsRasterMinMaxOrigin &origin)
Sets origin of min/max values.
Type colorRampType() const
Get the color ramp type.
void userHasSetManualMinMaxValues()
Uncheck cumulative cut, min/max, std-dev radio buttons.
QgsRectangle extent()
Return the extent selected by the user.
Assigns the color of the exact matching value in the color ramp item list.
void setMapCanvas(QgsMapCanvas *canvas)
Sets the map canvas associated with the widget.
Uses breaks from color palette.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=NoSection) const
Returns the value for setting key.
void setClassificationMode(ClassificationMode classificationMode)
Sets classification mode.
void classify()
Executes the single band pseudo raster classficiation.
QgsSingleBandPseudoColorRendererWidget(QgsRasterLayer *layer, const QgsRectangle &extent=QgsRectangle())
bool clip() const
Returns whether the shader will clip values which are out of range.
void setBand(int bandNo)
Sets the band used by the renderer.
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.
void load(int bandNo, double min, double max)
signal emitted when new min/max values are computed from statistics.
virtual QString max(int index=0)
void createShader(QgsColorRamp *colorRamp=nullptr, QgsColorRampShader::Type colorRampType=QgsColorRampShader::Interpolated, QgsColorRampShader::ClassificationMode classificationMode=QgsColorRampShader::Continuous, int classes=0, bool clip=false, const QgsRectangle &extent=QgsRectangle())
Creates a color ramp shader.
ClassificationMode
Classification modes used to create the color ramp shader.
Assigns the color of the higher class for every pixel between two class breaks.
ClassificationMode classificationMode() const
Returns the classification mode.
QgsMapCanvas * mCanvas
Associated map canvas.
QgsColorRamp * sourceColorRamp() const
Get the source color ramp.
void widgetChanged()
Emitted when something on the widget has changed.
Raster renderer pipe that applies colors to a raster.
Limits limits() const
Return limits.
void doComputations()
Load programmatically with current values.
int band() const
Returns the band used by the renderer.
void loadMinMax(int bandNo, double min, double max)
called when new min/max values are loaded
Base class for raster data providers.
void setFromMinMaxOrigin(const QgsRasterMinMaxOrigin &)
Set the "source" of min/max values.