QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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
19
20#include "qgscolordialog.h"
21#include "qgscolorramp.h"
22#include "qgscolorrampbutton.h"
24#include "qgsfileutils.h"
25#include "qgsguiutils.h"
29#include "qgssettings.h"
30#include "qgstreewidgetitem.h"
31
32#include <QCursor>
33#include <QFileDialog>
34#include <QInputDialog>
35#include <QMenu>
36#include <QMessageBox>
37#include <QPushButton>
38#include <QTextStream>
39#include <QTreeView>
40
41#include "moc_qgscolorrampshaderwidget.cpp"
42
44 : QWidget( parent )
45{
46 QgsSettings settings;
47
48 setupUi( this );
49 mLoadFromBandButton->setVisible( false ); // only for raster version
50
51 connect( mAddEntryButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mAddEntryButton_clicked );
52 connect( mDeleteEntryButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mDeleteEntryButton_clicked );
53 connect( mLoadFromBandButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mLoadFromBandButton_clicked );
54 connect( mLoadFromFileButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mLoadFromFileButton_clicked );
55 connect( mExportToFileButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::mExportToFileButton_clicked );
56 connect( mUnitLineEdit, &QLineEdit::textEdited, this, &QgsColorRampShaderWidget::mUnitLineEdit_textEdited );
57 connect( mColormapTreeWidget, &QTreeWidget::itemDoubleClicked, this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemDoubleClicked );
58 connect( mColorInterpolationComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsColorRampShaderWidget::mColorInterpolationComboBox_currentIndexChanged );
59 connect( mClassificationModeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsColorRampShaderWidget::mClassificationModeComboBox_currentIndexChanged );
60
61 connect( mLegendSettingsButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::showLegendSettings );
62
63 contextMenu = new QMenu( tr( "Options" ), this );
64 contextMenu->addAction( tr( "Change Color…" ), this, &QgsColorRampShaderWidget::changeColor );
65 contextMenu->addAction( tr( "Change Opacity…" ), this, &QgsColorRampShaderWidget::changeOpacity );
66
67 mColormapTreeWidget->setItemDelegateForColumn( ColorColumn, new QgsColorSwatchDelegate( this ) );
68 mValueDelegate = new QgsLocaleAwareNumericLineEditDelegate( Qgis::DataType::UnknownDataType, this );
69 mColormapTreeWidget->setItemDelegateForColumn( ValueColumn, mValueDelegate );
70
71 mColormapTreeWidget->setColumnWidth( ColorColumn, Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 6.6 );
72
73 mColormapTreeWidget->setContextMenuPolicy( Qt::CustomContextMenu );
74 mColormapTreeWidget->setSelectionMode( QAbstractItemView::ExtendedSelection );
75 connect( mColormapTreeWidget, &QTreeView::customContextMenuRequested, this, [this]( QPoint ) { contextMenu->exec( QCursor::pos() ); } );
76
77 QString defaultPalette = settings.value( QStringLiteral( "Raster/defaultPalette" ), "" ).toString();
78 btnColorRamp->setColorRampFromName( defaultPalette );
79
80 mColorInterpolationComboBox->addItem( tr( "Discrete" ), QVariant::fromValue( Qgis::ShaderInterpolationMethod::Discrete ) );
81 mColorInterpolationComboBox->addItem( tr( "Linear" ), QVariant::fromValue( Qgis::ShaderInterpolationMethod::Linear ) );
82 mColorInterpolationComboBox->addItem( tr( "Exact" ), QVariant::fromValue( Qgis::ShaderInterpolationMethod::Exact ) );
83 mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QVariant::fromValue( Qgis::ShaderInterpolationMethod::Linear ) ) );
84
85 mClassificationModeComboBox->addItem( tr( "Continuous" ), QVariant::fromValue( Qgis::ShaderClassificationMethod::Continuous ) );
86 mClassificationModeComboBox->addItem( tr( "Equal Interval" ), QVariant::fromValue( Qgis::ShaderClassificationMethod::EqualInterval ) );
87 // Quantile added only on demand
88 mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QVariant::fromValue( Qgis::ShaderClassificationMethod::Continuous ) ) );
89
90 mNumberOfEntriesSpinBox->setValue( 5 ); // some default
91
92 mClassificationModeComboBox_currentIndexChanged( 0 );
93
94 resetClassifyButton();
95
96 connect( mClassificationModeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsColorRampShaderWidget::classify );
97 connect( mColorInterpolationComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsColorRampShaderWidget::classify );
98 connect( mClassifyButton, &QPushButton::clicked, this, &QgsColorRampShaderWidget::classify );
99 connect( btnColorRamp, &QgsColorRampButton::colorRampChanged, this, &QgsColorRampShaderWidget::applyColorRamp );
100 connect( mNumberOfEntriesSpinBox, static_cast<void ( QSpinBox::* )( int )>( &QSpinBox::valueChanged ), this, &QgsColorRampShaderWidget::classify );
101 connect( mClipCheckBox, &QAbstractButton::toggled, this, &QgsColorRampShaderWidget::widgetChanged );
102 connect( mLabelPrecisionSpinBox, qOverload<int>( &QSpinBox::valueChanged ), this, [this]( int ) {
103 autoLabel();
104
105 if ( !mBlockChanges )
106 emit widgetChanged();
107 } );
108}
109
111
113{
114 Q_ASSERT( mClassificationModeComboBox->findData( QVariant::fromValue( Qgis::ShaderClassificationMethod::Quantile ) ) < 0 );
115 mClassificationModeComboBox->addItem( tr( "Quantile" ), QVariant::fromValue( Qgis::ShaderClassificationMethod::Quantile ) );
116}
117
119{
120 mRasterDataProvider = dp;
121 mLoadFromBandButton->setVisible( static_cast<bool>( mRasterDataProvider ) ); // only for raster version
122}
123
125{
126 mBand = band;
127 // Assume double by default
128 Qgis::DataType dataType { ( mRasterDataProvider && mBand > 0 ) ? mRasterDataProvider->dataType( mBand ) : Qgis::DataType::Float64 };
129
130 // Set the maximum number of digits in the precision spin box
131 const int maxDigits { QgsGuiUtils::significantDigits( dataType ) };
132 mLabelPrecisionSpinBox->setMaximum( maxDigits );
133 mValueDelegate->setDataType( dataType );
134}
135
137{
138 mExtent = extent;
139}
140
142{
143 QgsColorRampShader colorRampShader( mMin, mMax );
144 colorRampShader.setLabelPrecision( mLabelPrecisionSpinBox->value() );
145 colorRampShader.setColorRampType( mColorInterpolationComboBox->currentData().value<Qgis::ShaderInterpolationMethod>() );
146 colorRampShader.setClassificationMode( mClassificationModeComboBox->currentData().value<Qgis::ShaderClassificationMethod>() );
147 colorRampShader.setClip( mClipCheckBox->isChecked() );
148
149 //iterate through mColormapTreeWidget and set colormap info of layer
150 QList<QgsColorRampShader::ColorRampItem> colorRampItems;
151 int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
152 QTreeWidgetItem *currentItem = nullptr;
153 for ( int i = 0; i < topLevelItemCount; ++i )
154 {
155 currentItem = mColormapTreeWidget->topLevelItem( i );
156 if ( !currentItem )
157 {
158 continue;
159 }
160 QgsColorRampShader::ColorRampItem newColorRampItem;
161 newColorRampItem.value = currentItem->data( ValueColumn, Qt::ItemDataRole::DisplayRole ).toDouble();
162 newColorRampItem.color = currentItem->data( ColorColumn, Qt::ItemDataRole::EditRole ).value<QColor>();
163 newColorRampItem.label = currentItem->text( LabelColumn );
164 colorRampItems.append( newColorRampItem );
165 }
166 // sort the shader items
167 std::sort( colorRampItems.begin(), colorRampItems.end() );
168 colorRampShader.setColorRampItemList( colorRampItems );
169
170 if ( !btnColorRamp->isNull() )
171 {
172 colorRampShader.setSourceColorRamp( btnColorRamp->colorRamp() );
173 }
174
175 colorRampShader.setLegendSettings( new QgsColorRampLegendNodeSettings( mLegendSettings ) );
176 return colorRampShader;
177}
178
179void QgsColorRampShaderWidget::autoLabel()
180{
181 mColormapTreeWidget->sortItems( ValueColumn, Qt::AscendingOrder );
182
183#ifdef QGISDEBUG
184 dumpClasses();
185#endif
186
187 const QString unit = mUnitLineEdit->text();
188 int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
189
190 QTreeWidgetItem *currentItem = nullptr;
191 for ( int i = 0; i < topLevelItemCount; ++i )
192 {
193 currentItem = mColormapTreeWidget->topLevelItem( i );
194 //If the item is null or does not have a pixel values set, skip
195 if ( !currentItem || currentItem->data( ValueColumn, Qt::ItemDataRole::DisplayRole ).toString().isEmpty() )
196 {
197 continue;
198 }
199
200 const QString lbl = createLabel( currentItem, i, unit );
201
202 if ( currentItem->text( LabelColumn ).isEmpty() || currentItem->text( LabelColumn ) == lbl || currentItem->foreground( LabelColumn ).color() == QColor( Qt::gray ) )
203 {
204 currentItem->setText( LabelColumn, lbl );
205 currentItem->setForeground( LabelColumn, QBrush( QColor( Qt::gray ) ) );
206 }
207 }
208}
209
210void QgsColorRampShaderWidget::setUnitFromLabels()
211{
212 QStringList allSuffixes;
213 QString label;
214 int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
215 QTreeWidgetItem *currentItem = nullptr;
216 for ( int i = 0; i < topLevelItemCount; ++i )
217 {
218 currentItem = mColormapTreeWidget->topLevelItem( i );
219 //If the item is null or does not have a pixel values set, skip
220 if ( !currentItem || currentItem->text( ValueColumn ).isEmpty() )
221 {
222 continue;
223 }
224
225 label = createLabel( currentItem, i, QString() );
226
227 if ( currentItem->text( LabelColumn ).startsWith( label ) )
228 {
229 allSuffixes.append( currentItem->text( LabelColumn ).mid( label.length() ) );
230 }
231 }
232 // find most common suffix
233 QStringList suffixes = QStringList( allSuffixes );
234 suffixes.removeDuplicates();
235 int max = 0;
236 QString unit;
237 for ( int i = 0; i < suffixes.count(); ++i )
238 {
239 int n = allSuffixes.count( suffixes[i] );
240 if ( n > max )
241 {
242 max = n;
243 unit = suffixes[i];
244 }
245 }
246 // Set this suffix as unit if at least used twice
247 if ( max >= 2 )
248 {
249 mUnitLineEdit->setText( unit );
250 }
251}
252
253#ifdef QGISDEBUG
254void QgsColorRampShaderWidget::dumpClasses()
255{
256 for ( int row = 0; row < mColormapTreeWidget->model()->rowCount(); ++row )
257 {
258 const auto labelData { mColormapTreeWidget->model()->itemData( mColormapTreeWidget->model()->index( row, LabelColumn ) ) };
259 const auto valueData { mColormapTreeWidget->model()->itemData( mColormapTreeWidget->model()->index( row, ValueColumn ) ) };
260 QgsDebugMsgLevel( QStringLiteral( "Class %1 : %2 %3" ).arg( row ).arg( labelData[Qt::ItemDataRole::DisplayRole].toString(), valueData[Qt::ItemDataRole::DisplayRole].toString() ), 2 );
261 }
262}
263#endif
264
265void QgsColorRampShaderWidget::mAddEntryButton_clicked()
266{
267 QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
268 newItem->setData( ValueColumn, Qt::ItemDataRole::DisplayRole, 0 );
269 newItem->setData( ColorColumn, Qt::ItemDataRole::EditRole, QColor( Qt::magenta ) );
270 newItem->setText( LabelColumn, QString() );
271 newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
272 connect( newItem, &QgsTreeWidgetItemObject::itemEdited, this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited );
273 autoLabel();
274
276 updateColorRamp();
277 emit widgetChanged();
278}
279
280void QgsColorRampShaderWidget::mDeleteEntryButton_clicked()
281{
282 QList<QTreeWidgetItem *> itemList;
283 itemList = mColormapTreeWidget->selectedItems();
284 if ( itemList.isEmpty() )
285 {
286 return;
287 }
288
289 const auto constItemList = itemList;
290 for ( QTreeWidgetItem *item : constItemList )
291 {
292 delete item;
293 }
294
296 updateColorRamp();
297 emit widgetChanged();
298}
299
301{
302 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
303 if ( !ramp || std::isnan( mMin ) || std::isnan( mMax ) )
304 {
305 return;
306 }
307
308 std::unique_ptr<QgsColorRampShader> colorRampShader( new QgsColorRampShader(
309 mMin, mMax,
310 ramp.release(),
311 mColorInterpolationComboBox->currentData().value<Qgis::ShaderInterpolationMethod>(),
312 mClassificationModeComboBox->currentData().value<Qgis::ShaderClassificationMethod>()
313 )
314 );
315
316 // only for Quantile we need band and provider and extent
317 colorRampShader->classifyColorRamp( mNumberOfEntriesSpinBox->value(), mBand, mExtent, mRasterDataProvider );
318 colorRampShader->setClip( mClipCheckBox->isChecked() );
319
320 mColormapTreeWidget->clear();
321
322 const QList<QgsColorRampShader::ColorRampItem> colorRampItemList = colorRampShader->colorRampItemList();
323 QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItemList.constBegin();
324 for ( ; it != colorRampItemList.end(); ++it )
325 {
326 QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
327 newItem->setData( ValueColumn, Qt::ItemDataRole::DisplayRole, it->value );
328 newItem->setData( ColorColumn, Qt::ItemDataRole::EditRole, it->color );
329 newItem->setText( LabelColumn, QString() ); // Labels will be populated in autoLabel()
330 newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
331 connect( newItem, &QgsTreeWidgetItemObject::itemEdited, this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited );
332 }
333
334 mClipCheckBox->setChecked( colorRampShader->clip() );
335
336 autoLabel();
337 emit widgetChanged();
338}
339
340void QgsColorRampShaderWidget::mClassificationModeComboBox_currentIndexChanged( int index )
341{
342 Qgis::ShaderClassificationMethod mode = mClassificationModeComboBox->itemData( index ).value<Qgis::ShaderClassificationMethod>();
343 mNumberOfEntriesSpinBox->setEnabled( mode != Qgis::ShaderClassificationMethod::Continuous );
344 emit classificationModeChanged( mode );
345}
346
347void QgsColorRampShaderWidget::updateColorRamp()
348{
349 std::unique_ptr<QgsColorRamp> ramp( shader().createColorRamp() );
350 whileBlocking( btnColorRamp )->setColorRamp( ramp.get() );
351}
352
353void QgsColorRampShaderWidget::applyColorRamp()
354{
355 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
356 if ( !ramp )
357 {
358 return;
359 }
360
361 if ( !btnColorRamp->colorRampName().isEmpty() )
362 {
363 // Remember last used color ramp
364 QgsSettings settings;
365 settings.setValue( QStringLiteral( "Raster/defaultPalette" ), btnColorRamp->colorRampName() );
366 }
367
368 bool enableContinuous = ( ramp->count() > 0 );
369 mClassificationModeComboBox->setEnabled( enableContinuous );
370 if ( !enableContinuous )
371 {
372 mClassificationModeComboBox->setCurrentIndex( mClassificationModeComboBox->findData( QVariant::fromValue( Qgis::ShaderClassificationMethod::EqualInterval ) ) );
373 }
374
375 int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
376 if ( topLevelItemCount > 0 )
377 {
378 // We need to have valid min/max values here. If we haven't, load from colormap
379 double min, max;
380 if ( std::isnan( mMin ) || std::isnan( mMax ) )
381 {
382 colormapMinMax( min, max );
383 }
384 else
385 {
386 min = mMin;
387 max = mMax;
388 }
389
390 // if the list values has been customized, maintain pre-existing values
391 QTreeWidgetItem *currentItem = nullptr;
392 for ( int i = 0; i < topLevelItemCount; ++i )
393 {
394 currentItem = mColormapTreeWidget->topLevelItem( i );
395 if ( !currentItem )
396 {
397 continue;
398 }
399
400 double value = currentItem->data( ValueColumn, Qt::ItemDataRole::EditRole ).toDouble();
401 double position = ( value - min ) / ( max - min );
402 whileBlocking( static_cast<QgsTreeWidgetItemObject *>( currentItem ) )->setData( ColorColumn, Qt::ItemDataRole::EditRole, ramp->color( position ) );
403 }
404
405 emit widgetChanged();
406 }
407 else
408 {
409 classify();
410 }
411}
412
413void QgsColorRampShaderWidget::populateColormapTreeWidget( const QList<QgsColorRampShader::ColorRampItem> &colorRampItems )
414{
415 mColormapTreeWidget->clear();
416 QList<QgsColorRampShader::ColorRampItem>::const_iterator it = colorRampItems.constBegin();
417 int i = 0;
418 for ( ; it != colorRampItems.constEnd(); ++it )
419 {
420 QgsTreeWidgetItemObject *newItem = new QgsTreeWidgetItemObject( mColormapTreeWidget );
421 newItem->setData( ValueColumn, Qt::ItemDataRole::DisplayRole, it->value );
422 newItem->setData( ColorColumn, Qt::ItemDataRole::EditRole, it->color );
423 newItem->setText( LabelColumn, it->label );
424 newItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable );
425 connect( newItem, &QgsTreeWidgetItemObject::itemEdited, this, &QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited );
426 ++i;
427 }
428
429#ifdef QGISDEBUG
430 dumpClasses();
431#endif
432
433 setUnitFromLabels();
434
435 // Now we have the suffix
436 const QString unit = mUnitLineEdit->text();
437 for ( i = 0; i < mColormapTreeWidget->topLevelItemCount(); i++ )
438 {
439 QgsTreeWidgetItemObject *currentItem { static_cast<QgsTreeWidgetItemObject *>( mColormapTreeWidget->topLevelItem( i ) ) };
440 QString lbl { createLabel( currentItem, i, unit ) };
441 if ( currentItem->text( LabelColumn ).isEmpty() || currentItem->text( LabelColumn ) == lbl || currentItem->foreground( LabelColumn ).color() == QColor( Qt::gray ) )
442 {
443 currentItem->setText( LabelColumn, lbl );
444 currentItem->setForeground( LabelColumn, QBrush( QColor( Qt::gray ) ) );
445 }
446 }
447}
448
449void QgsColorRampShaderWidget::mLoadFromBandButton_clicked()
450{
451 if ( !mRasterDataProvider )
452 return;
453
454 QList<QgsColorRampShader::ColorRampItem> colorRampList = mRasterDataProvider->colorTable( mBand );
455 if ( !colorRampList.isEmpty() )
456 {
457 populateColormapTreeWidget( colorRampList );
458 mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QVariant::fromValue( Qgis::ShaderInterpolationMethod::Linear ) ) );
459 }
460 else
461 {
462 QMessageBox::warning( this, tr( "Load Color Map" ), tr( "The color map for band %1 has no entries." ).arg( mBand ) );
463 }
465 emit widgetChanged();
466}
467
468void QgsColorRampShaderWidget::mLoadFromFileButton_clicked()
469{
470 QgsSettings settings;
471 QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
472 const QString fileName = QFileDialog::getOpenFileName( this, tr( "Load Color Map from File" ), lastDir, tr( "Textfile (*.txt)" ) );
473 if ( fileName.isEmpty() )
474 return;
475
476 QList<QgsColorRampShader::ColorRampItem> colorRampItems;
478 QStringList errors;
479 if ( QgsRasterRendererUtils::parseColorMapFile( fileName, colorRampItems, type, errors ) )
480 {
481 //clear the current tree
482 mColormapTreeWidget->clear();
483
484 mColorInterpolationComboBox->setCurrentIndex( mColorInterpolationComboBox->findData( QVariant::fromValue( type ) ) );
485
486 populateColormapTreeWidget( colorRampItems );
487
488 if ( !errors.empty() )
489 {
490 QMessageBox::warning( this, tr( "Load Color Map from File" ), tr( "The following lines contained errors\n\n" ) + errors.join( '\n' ) );
491 }
492 }
493 else
494 {
495 const QString error = tr( "An error occurred while reading the color map\n\n" ) + errors.join( '\n' );
496 QMessageBox::warning( this, tr( "Load Color Map from File" ), error );
497 }
498
499 QFileInfo fileInfo( fileName );
500 settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
501
503 updateColorRamp();
504 emit widgetChanged();
505}
506
507void QgsColorRampShaderWidget::mExportToFileButton_clicked()
508{
509 QgsSettings settings;
510 QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
511 QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Color Map as File" ), lastDir, tr( "Textfile (*.txt)" ) );
512 if ( fileName.isEmpty() )
513 return;
514
515 fileName = QgsFileUtils::ensureFileNameHasExtension( fileName, QStringList() << QStringLiteral( "txt" ) );
516
517 QList<QgsColorRampShader::ColorRampItem> colorRampItems;
518 int topLevelItemCount = mColormapTreeWidget->topLevelItemCount();
519 for ( int i = 0; i < topLevelItemCount; ++i )
520 {
521 QTreeWidgetItem *currentItem = mColormapTreeWidget->topLevelItem( i );
522 if ( !currentItem )
523 {
524 continue;
525 }
526
527 QgsColorRampShader::ColorRampItem item;
528 item.value = currentItem->data( ValueColumn, Qt::ItemDataRole::DisplayRole ).toDouble();
529 item.color = currentItem->data( ColorColumn, Qt::ItemDataRole::EditRole ).value<QColor>();
530 item.label = currentItem->text( LabelColumn );
531 colorRampItems << item;
532 }
533
534 if ( !QgsRasterRendererUtils::saveColorMapFile( fileName, colorRampItems, mColorInterpolationComboBox->currentData().value<Qgis::ShaderInterpolationMethod>() ) )
535 {
536 QMessageBox::warning( this, tr( "Save Color Map as File" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
537 }
538
539 QFileInfo fileInfo( fileName );
540 settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
541}
542
543void QgsColorRampShaderWidget::mUnitLineEdit_textEdited( const QString & )
544{
545 autoLabel();
546
547 if ( !mBlockChanges )
548 emit widgetChanged();
549}
550
551void QgsColorRampShaderWidget::mColormapTreeWidget_itemDoubleClicked( QTreeWidgetItem *item, int column )
552{
553 if ( !item )
554 {
555 return;
556 }
557
558 if ( column == LabelColumn )
559 {
560 // Set text color to default black, which signifies a manually edited label
561 item->setForeground( LabelColumn, QBrush() );
562 }
563}
564
565void QgsColorRampShaderWidget::mColormapTreeWidget_itemEdited( QTreeWidgetItem *item, int column )
566{
567 Q_UNUSED( item )
568
569 switch ( column )
570 {
571 case ValueColumn:
572 {
573 autoLabel();
575 updateColorRamp();
576 emit widgetChanged();
577 break;
578 }
579
580 case LabelColumn:
581 {
582 // call autoLabel to fill when empty or gray out when same as autoLabel
583 autoLabel();
584 emit widgetChanged();
585 break;
586 }
587
588 case ColorColumn:
589 {
591 updateColorRamp();
592 emit widgetChanged();
593 break;
594 }
595 }
596}
597
599{
600 mBlockChanges++;
601
602 // Those objects are connected to classify() the color ramp shader if they change, or call widget change
603 // need to block them to avoid to classify and to alter the color ramp, or to call duplicate widget change
604 whileBlocking( mClipCheckBox )->setChecked( colorRampShader.clip() );
605 whileBlocking( mColorInterpolationComboBox )->setCurrentIndex( mColorInterpolationComboBox->findData( QVariant::fromValue( colorRampShader.colorRampType() ) ) );
606 mColorInterpolationComboBox_currentIndexChanged( mColorInterpolationComboBox->currentIndex() );
607 whileBlocking( mClassificationModeComboBox )->setCurrentIndex( mClassificationModeComboBox->findData( QVariant::fromValue( colorRampShader.classificationMode() ) ) );
608 mClassificationModeComboBox_currentIndexChanged( mClassificationModeComboBox->currentIndex() );
609 whileBlocking( mNumberOfEntriesSpinBox )->setValue( colorRampShader.colorRampItemList().count() ); // some default
610
611 if ( colorRampShader.sourceColorRamp() )
612 {
613 whileBlocking( btnColorRamp )->setColorRamp( colorRampShader.sourceColorRamp() );
614 }
615 else
616 {
617 QgsSettings settings;
618 QString defaultPalette = settings.value( QStringLiteral( "/Raster/defaultPalette" ), "Spectral" ).toString();
619 btnColorRamp->setColorRampFromName( defaultPalette );
620 }
621
622 mLabelPrecisionSpinBox->setValue( colorRampShader.labelPrecision() );
623
625
626 if ( colorRampShader.legendSettings() )
627 mLegendSettings = *colorRampShader.legendSettings();
628
629 mBlockChanges--;
630 emit widgetChanged();
631}
632
633void QgsColorRampShaderWidget::mColorInterpolationComboBox_currentIndexChanged( int index )
634{
635 Qgis::ShaderInterpolationMethod interpolation = mColorInterpolationComboBox->itemData( index ).value<Qgis::ShaderInterpolationMethod>();
636
637 mClipCheckBox->setEnabled( interpolation == Qgis::ShaderInterpolationMethod::Linear );
638
639 QString valueLabel;
640 QString valueToolTip;
641 switch ( interpolation )
642 {
644 valueLabel = tr( "Value" );
645 valueToolTip = tr( "Value for color stop" );
646 mLegendSettingsButton->setEnabled( true );
647 break;
649 valueLabel = tr( "Value <=" );
650 valueToolTip = tr( "Maximum value for class" );
651 mLegendSettingsButton->setEnabled( false );
652 break;
654 valueLabel = tr( "Value =" );
655 valueToolTip = tr( "Value for color" );
656 mLegendSettingsButton->setEnabled( false );
657 break;
658 }
659
660 QTreeWidgetItem *header = mColormapTreeWidget->headerItem();
661 header->setText( ValueColumn, valueLabel );
662 header->setToolTip( ValueColumn, valueToolTip );
663
664 autoLabel();
665 emit widgetChanged();
666}
667
669{
670 if ( !qgsDoubleNear( mMin, min ) || !qgsDoubleNear( mMax, max ) )
671 {
672 setMinimumMaximum( min, max );
673 classify();
674 }
675}
676
678{
679 mMin = min;
680 mMax = max;
681 resetClassifyButton();
682}
683
685{
686 return mMin;
687}
688
690{
691 return mMax;
692}
693
694bool QgsColorRampShaderWidget::colormapMinMax( double &min, double &max ) const
695{
696 QTreeWidgetItem *item = mColormapTreeWidget->topLevelItem( 0 );
697 if ( !item )
698 {
699 return false;
700 }
701
702 // If using discrete, the first and last items contain the upper and lower
703 // values of the first and last classes, we don't want these values but real min/max
704 if ( !std::isnan( mMin ) && !std::isnan( mMax ) && mColorInterpolationComboBox->currentData().value<Qgis::ShaderInterpolationMethod>() == Qgis::ShaderInterpolationMethod::Discrete )
705 {
706 min = mMin;
707 max = mMax;
708 }
709 else
710 {
711 min = item->data( ValueColumn, Qt::ItemDataRole::DisplayRole ).toDouble();
712 item = mColormapTreeWidget->topLevelItem( mColormapTreeWidget->topLevelItemCount() - 1 );
713 max = item->data( ValueColumn, Qt::ItemDataRole::DisplayRole ).toDouble();
714 }
715 return true;
716}
717
719{
720 double min = 0, max = 0;
721 if ( !colormapMinMax( min, max ) )
722 {
723 return;
724 }
725
726 if ( !qgsDoubleNear( mMin, min ) || !qgsDoubleNear( mMax, max ) )
727 {
728 mMin = min;
729 mMax = max;
730 emit minimumMaximumChangedFromTree( min, max );
731 }
732}
733
734void QgsColorRampShaderWidget::resetClassifyButton()
735{
736 mClassifyButton->setEnabled( true );
737 if ( std::isnan( mMin ) || std::isnan( mMax ) || mMin >= mMax )
738 {
739 mClassifyButton->setEnabled( false );
740 }
741}
742
743QString QgsColorRampShaderWidget::createLabel( QTreeWidgetItem *currentItem, int row, const QString unit )
744{
745 auto applyPrecision = [this]( const QString &value ) {
746 double val { value.toDouble() };
747 Qgis::DataType dataType { mRasterDataProvider ? mRasterDataProvider->dataType( mBand ) : Qgis::DataType::Float64 };
748 switch ( dataType )
749 {
760 {
761 return QLocale().toString( std::round( val ), 'f', 0 );
762 }
765 {
766 if ( mLabelPrecisionSpinBox->value() < 0 )
767 {
768 const double factor = std::pow( 10, -mLabelPrecisionSpinBox->value() );
769 val = static_cast<qlonglong>( val / factor ) * factor;
770 return QLocale().toString( val, 'f', 0 );
771 }
772 return QLocale().toString( val, 'f', mLabelPrecisionSpinBox->value() );
773 }
777 {
778 if ( mLabelPrecisionSpinBox->value() < 0 )
779 {
780 const double factor = std::pow( 10, -mLabelPrecisionSpinBox->value() );
781 val = static_cast<qlonglong>( val / factor ) * factor;
782 return QLocale().toString( val, 'f', 0 );
783 }
784 return QLocale().toString( val, 'f', mLabelPrecisionSpinBox->value() );
785 }
786 }
787 return QString();
788 };
789
790 Qgis::ShaderInterpolationMethod interpolation = mColorInterpolationComboBox->currentData().value<Qgis::ShaderInterpolationMethod>();
791 bool discrete = interpolation == Qgis::ShaderInterpolationMethod::Discrete;
792 QString lbl;
793
794 if ( discrete )
795 {
796 if ( row == 0 )
797 {
798 lbl = "<= " + applyPrecision( currentItem->data( ValueColumn, Qt::ItemDataRole::DisplayRole ).toString() ) + unit;
799 }
800 else if ( currentItem->data( ValueColumn, Qt::ItemDataRole::DisplayRole ).toDouble() == std::numeric_limits<double>::infinity() )
801 {
802 lbl = "> " + applyPrecision( mColormapTreeWidget->topLevelItem( row - 1 )->data( ValueColumn, Qt::ItemDataRole::DisplayRole ).toString() ) + unit;
803 }
804 else
805 {
806 lbl = applyPrecision( mColormapTreeWidget->topLevelItem( row - 1 )->data( ValueColumn, Qt::ItemDataRole::DisplayRole ).toString() ) + " - " + applyPrecision( currentItem->data( ValueColumn, Qt::ItemDataRole::DisplayRole ).toString() ) + unit;
807 }
808 }
809 else
810 {
811 lbl = applyPrecision( currentItem->data( ValueColumn, Qt::ItemDataRole::DisplayRole ).toString() ) + unit;
812 }
813
814 return lbl;
815}
816
817void QgsColorRampShaderWidget::changeColor()
818{
819 QList<QTreeWidgetItem *> itemList;
820 itemList = mColormapTreeWidget->selectedItems();
821 if ( itemList.isEmpty() )
822 {
823 return;
824 }
825 QTreeWidgetItem *firstItem = itemList.first();
826
827 QColor currentColor = firstItem->data( ColorColumn, Qt::ItemDataRole::EditRole ).value<QColor>();
828 QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast<QWidget *>( parent() ) );
829 if ( panel && panel->dockMode() )
830 {
831 QgsCompoundColorWidget *colorWidget = new QgsCompoundColorWidget( panel, currentColor, QgsCompoundColorWidget::LayoutVertical );
832 colorWidget->setPanelTitle( tr( "Select Color" ) );
833 colorWidget->setAllowOpacity( true );
834 connect( colorWidget, &QgsCompoundColorWidget::currentColorChanged, this, [this, itemList]( const QColor &newColor ) {
835 for ( QTreeWidgetItem *item : std::as_const( itemList ) )
836 {
837 item->setData( ColorColumn, Qt::ItemDataRole::EditRole, newColor );
838 }
839
841 emit widgetChanged();
842 } );
843 panel->openPanel( colorWidget );
844 }
845 else
846 {
847 // modal dialog version... yuck
848 QColor newColor = QgsColorDialog::getColor( currentColor, this, QStringLiteral( "Change Color" ), true );
849 if ( newColor.isValid() )
850 {
851 for ( QTreeWidgetItem *item : std::as_const( itemList ) )
852 {
853 item->setData( ColorColumn, Qt::ItemDataRole::EditRole, newColor );
854 }
855
857 emit widgetChanged();
858 }
859 }
860}
861
862void QgsColorRampShaderWidget::changeOpacity()
863{
864 QList<QTreeWidgetItem *> itemList;
865 itemList = mColormapTreeWidget->selectedItems();
866 if ( itemList.isEmpty() )
867 {
868 return;
869 }
870 QTreeWidgetItem *firstItem = itemList.first();
871
872 bool ok;
873 double oldOpacity = firstItem->data( ColorColumn, Qt::ItemDataRole::EditRole ).value<QColor>().alpha() / 255 * 100;
874 double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
875 if ( ok )
876 {
877 int newOpacity = static_cast<int>( opacity / 100 * 255 );
878 const auto constItemList = itemList;
879 for ( QTreeWidgetItem *item : constItemList )
880 {
881 QColor newColor = item->data( ColorColumn, Qt::ItemDataRole::EditRole ).value<QColor>();
882 newColor.setAlpha( newOpacity );
883 item->setData( ColorColumn, Qt::ItemDataRole::EditRole, newColor );
884 }
885
887 emit widgetChanged();
888 }
889}
890
891void QgsColorRampShaderWidget::showLegendSettings()
892{
893 QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast<QWidget *>( parent() ) );
894 if ( panel && panel->dockMode() )
895 {
896 QgsColorRampLegendNodeWidget *legendPanel = new QgsColorRampLegendNodeWidget();
897 legendPanel->setPanelTitle( tr( "Legend Settings" ) );
898 legendPanel->setSettings( mLegendSettings );
899 connect( legendPanel, &QgsColorRampLegendNodeWidget::widgetChanged, this, [this, legendPanel] {
900 mLegendSettings = legendPanel->settings();
901 emit widgetChanged();
902 } );
903 panel->openPanel( legendPanel );
904 }
905 else
906 {
907 QgsColorRampLegendNodeDialog dialog( mLegendSettings, this );
908 dialog.setWindowTitle( tr( "Legend Settings" ) );
909 if ( dialog.exec() )
910 {
911 mLegendSettings = dialog.settings();
912 emit widgetChanged();
913 }
914 }
915}
ShaderInterpolationMethod
Color ramp shader interpolation methods.
Definition qgis.h:1425
@ Exact
Assigns the color of the exact matching value in the color ramp item list.
Definition qgis.h:1428
@ Linear
Interpolates the color between two class breaks linearly.
Definition qgis.h:1426
@ Discrete
Assigns the color of the higher class for every pixel between two class breaks.
Definition qgis.h:1427
ShaderClassificationMethod
Color ramp shader classification methods.
Definition qgis.h:1440
@ Continuous
Uses breaks from color palette.
Definition qgis.h:1441
@ Quantile
Uses quantile (i.e. equal pixel) count.
Definition qgis.h:1443
@ EqualInterval
Uses equal interval.
Definition qgis.h:1442
DataType
Raster data types.
Definition qgis.h:372
@ CInt32
Complex Int32.
Definition qgis.h:383
@ Float32
Thirty two bit floating point (float).
Definition qgis.h:380
@ CFloat64
Complex Float64.
Definition qgis.h:385
@ Int16
Sixteen bit signed integer (qint16).
Definition qgis.h:377
@ ARGB32_Premultiplied
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32_Premultiplied.
Definition qgis.h:387
@ Int8
Eight bit signed integer (qint8) (added in QGIS 3.30).
Definition qgis.h:375
@ UInt16
Sixteen bit unsigned integer (quint16).
Definition qgis.h:376
@ Byte
Eight bit unsigned integer (quint8).
Definition qgis.h:374
@ UnknownDataType
Unknown or unspecified type.
Definition qgis.h:373
@ ARGB32
Color, alpha, red, green, blue, 4 bytes the same as QImage::Format_ARGB32.
Definition qgis.h:386
@ Int32
Thirty two bit signed integer (qint32).
Definition qgis.h:379
@ Float64
Sixty four bit floating point (double).
Definition qgis.h:381
@ CFloat32
Complex Float32.
Definition qgis.h:384
@ CInt16
Complex Int16.
Definition qgis.h:382
@ UInt32
Thirty two bit unsigned integer (quint32).
Definition qgis.h:378
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:6222
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), bool allowOpacity=false)
Returns a color selection from a color dialog.
void colorRampChanged()
Emitted whenever a new color ramp is set for the button.
Settings for a color ramp legend node.
QgsColorRampLegendNodeSettings settings() const
Returns the legend node settings as defined by the widget.
void setSettings(const QgsColorRampLegendNodeSettings &settings)
Sets the settings to show in the widget.
~QgsColorRampShaderWidget() override
void setExtent(const QgsRectangle &extent)
Sets extent, only when used for raster layer.
void setMinimumMaximumAndClassify(double minimum, double maximum)
Sets min max and classify color tree.
void initializeForUseWithRasterLayer()
Allows quantile classification mode for raster layers.
void classificationModeChanged(Qgis::ShaderClassificationMethod mode)
Classification mode changed.
void populateColormapTreeWidget(const QList< QgsColorRampShader::ColorRampItem > &colorRampItems)
Populates color ramp tree from ramp items.
QgsColorRampShaderWidget(QWidget *parent=nullptr)
Creates new color ramp shader widget.
void minimumMaximumChangedFromTree(double minimum, double maximum)
Color ramp tree has changed.
void classify()
Executes the single band pseudo raster classification.
void widgetChanged()
Widget changed.
double minimum() const
Gets min value.
void setFromShader(const QgsColorRampShader &colorRampShader)
Sets widget state from the color ramp shader.
double maximum() const
Gets max value.
void setMinimumMaximum(double minimum, double maximum)
Sets min max.
QgsColorRampShader shader() const
Returns shared function used in the renderer.
void setRasterBand(int band)
Sets raster band, only when used for raster layer.
void setRasterDataProvider(QgsRasterDataProvider *dp)
Associates raster with the widget, only when used for raster layer.
void loadMinimumMaximumFromTree()
Loads min and max values from color ramp tree.
A ramp shader will color a raster pixel based on a list of values ranges in a ramp.
Qgis::ShaderClassificationMethod classificationMode() const
Returns the classification mode.
Qgis::ShaderInterpolationMethod colorRampType() const
Returns the color ramp interpolation method.
const QgsColorRampLegendNodeSettings * legendSettings() const
Returns the color ramp shader legend settings.
void setSourceColorRamp(QgsColorRamp *colorramp)
Set the source color ramp.
void setClassificationMode(Qgis::ShaderClassificationMethod classificationMode)
Sets the classification mode.
QList< QgsColorRampShader::ColorRampItem > colorRampItemList() const
Returns the custom color map.
void setClip(bool clip)
Sets whether the shader should not render values out of range.
bool clip() const
Returns whether the shader will clip values which are out of range.
QgsColorRamp * sourceColorRamp() const
Returns the source color ramp.
void setColorRampType(Qgis::ShaderInterpolationMethod colorRampType)
Sets the color ramp interpolation method.
void setColorRampItemList(const QList< QgsColorRampShader::ColorRampItem > &list)
Sets a custom color map.
void setLegendSettings(QgsColorRampLegendNodeSettings *settings)
Sets the color ramp shader legend settings.
A delegate for showing a color swatch in a list.
@ LayoutVertical
Use a narrower, vertically stacked layout.
void currentColorChanged(const QColor &color)
Emitted when the dialog's color changes.
void setAllowOpacity(bool allowOpacity)
Sets whether opacity modification (transparency) is permitted for the color dialog.
static QString ensureFileNameHasExtension(const QString &fileName, const QStringList &extensions)
Ensures that a fileName ends with an extension from the provided list of extensions.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
bool dockMode() const
Returns the dock mode state.
void widgetChanged()
Emitted when the widget state changes.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
Base class for raster data providers.
static bool saveColorMapFile(const QString &path, const QList< QgsColorRampShader::ColorRampItem > &items, Qgis::ShaderInterpolationMethod type)
Exports a list of color ramp items and ramp shader type to a color map file at the specified path.
static bool parseColorMapFile(const QString &path, QList< QgsColorRampShader::ColorRampItem > &items, Qgis::ShaderInterpolationMethod &type, QStringList &errors)
Parses an exported color map file at the specified path and extracts the stored color ramp items and ...
void setLabelPrecision(int labelPrecision)
Sets label precision to labelPrecision.
int labelPrecision() const
Returns label precision.
A rectangle specified with double values.
Stores settings for use within QGIS.
Definition qgssettings.h:65
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Custom QgsTreeWidgetItem with extra signals when the item is edited.
void itemEdited(QTreeWidgetItem *item, int column)
Emitted when the contents of the column in the specified item has been edited by the user.
void setData(int column, int role, const QVariant &value) override
Sets the value for the item's column and role to the given value.
int significantDigits(const Qgis::DataType rasterDataType)
Returns the maximum number of significant digits a for the given rasterDataType.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6511
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61