QGIS API Documentation 3.29.0-Master (d0a9f12494)
qgspalettedrendererwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspalettedrendererwidget.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
21#include "qgsrasterlayer.h"
22#include "qgscolordialog.h"
23#include "qgssettings.h"
24#include "qgsproject.h"
25#include "qgscolorrampimpl.h"
27
28#include <QColorDialog>
29#include <QInputDialog>
30#include <QFileDialog>
31#include <QMessageBox>
32#include <QMenu>
33#include <QMimeData>
34#include <QTextStream>
35
36#ifdef ENABLE_MODELTEST
37#include "modeltest.h"
38#endif
39
40
42{
43 setupUi( this );
44
45 mCalculatingProgressBar->hide();
46 mCancelButton->hide();
47
48 mContextMenu = new QMenu( tr( "Options" ), this );
49 mContextMenu->addAction( tr( "Change Color…" ), this, SLOT( changeColor() ) );
50 mContextMenu->addAction( tr( "Change Opacity…" ), this, SLOT( changeOpacity() ) );
51 mContextMenu->addAction( tr( "Change Label…" ), this, SLOT( changeLabel() ) );
52
53 mAdvancedMenu = new QMenu( tr( "Advanced Options" ), this );
54 QAction *mLoadFromLayerAction = mAdvancedMenu->addAction( tr( "Load Classes from Layer" ) );
55 connect( mLoadFromLayerAction, &QAction::triggered, this, &QgsPalettedRendererWidget::loadFromLayer );
56 QAction *loadFromFile = mAdvancedMenu->addAction( tr( "Load Color Map from File…" ) );
57 connect( loadFromFile, &QAction::triggered, this, &QgsPalettedRendererWidget::loadColorTable );
58 QAction *exportToFile = mAdvancedMenu->addAction( tr( "Export Color Map to File…" ) );
59 connect( exportToFile, &QAction::triggered, this, &QgsPalettedRendererWidget::saveColorTable );
60
61
62 mButtonAdvanced->setMenu( mAdvancedMenu );
63
64 mModel = new QgsPalettedRendererModel( this );
65 mProxyModel = new QgsPalettedRendererProxyModel( this );
66 mProxyModel->setSourceModel( mModel );
67 mTreeView->setSortingEnabled( false );
68 mTreeView->setModel( mProxyModel );
69
70 connect( this, &QgsPalettedRendererWidget::widgetChanged, this, [ = ]
71 {
72 mProxyModel->sort( QgsPalettedRendererModel::Column::ValueColumn );
73 } );
74
75#ifdef ENABLE_MODELTEST
76 new ModelTest( mModel, this );
77#endif
78
79 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ColorColumn, new QgsColorSwatchDelegate( this ) );
80 mValueDelegate = new QgsLocaleAwareNumericLineEditDelegate( Qgis::DataType::UnknownDataType, this );
81 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ValueColumn, mValueDelegate );
82
83 mTreeView->setColumnWidth( QgsPalettedRendererModel::ColorColumn, Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 6.6 );
84 mTreeView->setContextMenuPolicy( Qt::CustomContextMenu );
85 mTreeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
86 mTreeView->setDragEnabled( true );
87 mTreeView->setAcceptDrops( true );
88 mTreeView->setDropIndicatorShown( true );
89 mTreeView->setDragDropMode( QAbstractItemView::InternalMove );
90 mTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
91 mTreeView->setDefaultDropAction( Qt::MoveAction );
92
93 connect( mTreeView, &QTreeView::customContextMenuRequested, this, [ = ]( QPoint ) { mContextMenu->exec( QCursor::pos() ); } );
94
95 btnColorRamp->setShowRandomColorRamp( true );
96
97 connect( btnColorRamp, &QgsColorRampButton::colorRampChanged, this, &QgsPalettedRendererWidget::applyColorRamp );
98
99 mBandComboBox->setLayer( mRasterLayer );
100
101 if ( mRasterLayer )
102 {
104 if ( !provider )
105 {
106 return;
107 }
109 }
110
112 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
113 connect( mDeleteEntryButton, &QPushButton::clicked, this, &QgsPalettedRendererWidget::deleteEntry );
114 connect( mButtonDeleteAll, &QPushButton::clicked, mModel, &QgsPalettedRendererModel::deleteAll );
115 connect( mAddEntryButton, &QPushButton::clicked, this, &QgsPalettedRendererWidget::addEntry );
116 connect( mClassifyButton, &QPushButton::clicked, this, &QgsPalettedRendererWidget::classify );
117
119 {
120 mLoadFromLayerAction->setEnabled( !mRasterLayer->dataProvider()->colorTable( mBandComboBox->currentBand() ).isEmpty() );
121 }
122 else
123 {
124 mLoadFromLayerAction->setEnabled( false );
125 }
126
127 connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( QgsMapLayer * ) >( &QgsProject::layerWillBeRemoved ), this, &QgsPalettedRendererWidget::layerWillBeRemoved );
128 connect( mBandComboBox, &QgsRasterBandComboBox::bandChanged, this, &QgsPalettedRendererWidget::bandChanged );
129}
130
132{
133 if ( mGatherer )
134 {
135 mGatherer->stop();
136 mGatherer->wait(); // mGatherer is deleted when wait completes
137 }
138}
139
141{
142 QgsPalettedRasterRenderer::ClassData classes = mProxyModel->classData();
143 int bandNumber = mBandComboBox->currentBand();
144
146 if ( !btnColorRamp->isNull() )
147 {
148 r->setSourceColorRamp( btnColorRamp->colorRamp() );
149 }
150 return r;
151}
152
154{
155 const QgsPalettedRasterRenderer *pr = dynamic_cast<const QgsPalettedRasterRenderer *>( r );
156 if ( pr )
157 {
158 mBand = pr->band();
159 whileBlocking( mBandComboBox )->setBand( mBand );
160
161 //read values and colors and fill into tree widget
162 mModel->setClassData( pr->classes() );
163
164 if ( pr->sourceColorRamp() )
165 {
166 whileBlocking( btnColorRamp )->setColorRamp( pr->sourceColorRamp() );
167 }
168 else
169 {
170 std::unique_ptr< QgsColorRamp > ramp( new QgsRandomColorRamp() );
171 whileBlocking( btnColorRamp )->setColorRamp( ramp.get() );
172 }
173 }
174 else
175 {
176 loadFromLayer();
177 std::unique_ptr< QgsColorRamp > ramp( new QgsRandomColorRamp() );
178 whileBlocking( btnColorRamp )->setColorRamp( ramp.get() );
179 }
180
182 {
183 mValueDelegate->setDataType( mRasterLayer->dataProvider()->dataType( mBand ) );
184 }
185}
186
187void QgsPalettedRendererWidget::setSelectionColor( const QItemSelection &selection, const QColor &color )
188{
189 // don't want to emit widgetChanged multiple times
190 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
191
192 QModelIndex colorIndex;
193 const auto constSelection = selection;
194 for ( const QItemSelectionRange &range : constSelection )
195 {
196 const auto constIndexes = range.indexes();
197 for ( const QModelIndex &index : constIndexes )
198 {
199 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
200 mModel->setData( colorIndex, color, Qt::EditRole );
201 }
202 }
203 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
204
205 emit widgetChanged();
206}
207
208void QgsPalettedRendererWidget::deleteEntry()
209{
210 // don't want to emit widgetChanged multiple times
211 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
212
213 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
214 const auto constSel = sel;
215 for ( const QItemSelectionRange &range : constSel )
216 {
217 if ( range.isValid() )
218 mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
219 }
220
221 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
222
223 emit widgetChanged();
224}
225
226void QgsPalettedRendererWidget::addEntry()
227{
228 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
229
230 QColor color( 150, 150, 150 );
231 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
232 if ( ramp )
233 {
234 color = ramp->color( 1.0 );
235 }
236 QModelIndex newEntry = mModel->addEntry( color );
237 mTreeView->scrollTo( newEntry );
238 mTreeView->selectionModel()->select( mProxyModel->mapFromSource( newEntry ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
239 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
240 emit widgetChanged();
241}
242
243void QgsPalettedRendererWidget::changeColor()
244{
245 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
246 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
247 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
248
249 QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast< QWidget * >( parent() ) );
250 if ( panel && panel->dockMode() )
251 {
253 colorWidget->setPanelTitle( tr( "Select Color" ) );
254 colorWidget->setAllowOpacity( true );
255 connect( colorWidget, &QgsCompoundColorWidget::currentColorChanged, this, [ = ]( const QColor & color ) { setSelectionColor( sel, color ); } );
256 panel->openPanel( colorWidget );
257 }
258 else
259 {
260 // modal dialog version... yuck
261 QColor newColor = QgsColorDialog::getColor( currentColor, this, QStringLiteral( "Change color" ), true );
262 if ( newColor.isValid() )
263 {
264 setSelectionColor( sel, newColor );
265 }
266 }
267}
268
269void QgsPalettedRendererWidget::changeOpacity()
270{
271 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
272 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
273 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
274
275 bool ok;
276 double oldOpacity = ( currentColor.alpha() / 255.0 ) * 100.0;
277 double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
278 if ( ok )
279 {
280 int newOpacity = opacity / 100 * 255;
281
282 // don't want to emit widgetChanged multiple times
283 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
284
285 const auto constSel = sel;
286 for ( const QItemSelectionRange &range : constSel )
287 {
288 const auto constIndexes = range.indexes();
289 for ( const QModelIndex &index : constIndexes )
290 {
291 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
292
293 QColor newColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
294 newColor.setAlpha( newOpacity );
295 mModel->setData( colorIndex, newColor, Qt::EditRole );
296 }
297 }
298 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
299
300 emit widgetChanged();
301 }
302}
303
304void QgsPalettedRendererWidget::changeLabel()
305{
306 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
307 QModelIndex labelIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::LabelColumn );
308 QString currentLabel = mModel->data( labelIndex, Qt::DisplayRole ).toString();
309
310 bool ok;
311 QString newLabel = QInputDialog::getText( this, tr( "Label" ), tr( "Change label" ), QLineEdit::Normal, currentLabel, &ok );
312 if ( ok )
313 {
314 // don't want to emit widgetChanged multiple times
315 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
316
317 const auto constSel = sel;
318 for ( const QItemSelectionRange &range : constSel )
319 {
320 const auto constIndexes = range.indexes();
321 for ( const QModelIndex &index : constIndexes )
322 {
323 labelIndex = mModel->index( index.row(), QgsPalettedRendererModel::LabelColumn );
324 mModel->setData( labelIndex, newLabel, Qt::EditRole );
325 }
326 }
327 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
328
329 emit widgetChanged();
330 }
331}
332
333void QgsPalettedRendererWidget::applyColorRamp()
334{
335 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
336 if ( !ramp )
337 {
338 return;
339 }
340
341 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
342
343 QgsPalettedRasterRenderer::ClassData data = mProxyModel->classData();
344 QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
345
346 double numberOfEntries = data.count();
347 int i = 0;
348
349 if ( QgsRandomColorRamp *randomRamp = dynamic_cast<QgsRandomColorRamp *>( ramp.get() ) )
350 {
351 //ramp is a random colors ramp, so inform it of the total number of required colors
352 //this allows the ramp to pregenerate a set of visually distinctive colors
353 randomRamp->setTotalColorCount( numberOfEntries );
354 }
355
356 if ( numberOfEntries > 1 )
357 numberOfEntries -= 1; //avoid duplicate first color
358
359 for ( ; cIt != data.end(); ++cIt )
360 {
361 cIt->color = ramp->color( i / numberOfEntries );
362 i++;
363 }
364 mModel->setClassData( data );
365
366 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
367 emit widgetChanged();
368}
369
370void QgsPalettedRendererWidget::loadColorTable()
371{
372 QgsSettings settings;
373 QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
374 QString fileName = QFileDialog::getOpenFileName( this, tr( "Load Color Table from File" ), lastDir );
375 if ( !fileName.isEmpty() )
376 {
378 if ( !classes.isEmpty() )
379 {
380 disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
381 mModel->setClassData( classes );
382 emit widgetChanged();
383 connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
384 }
385 else
386 {
387 QMessageBox::critical( nullptr, tr( "Load Color Table" ), tr( "Could not interpret file as a raster color table." ) );
388 }
389 }
390}
391
392void QgsPalettedRendererWidget::saveColorTable()
393{
394 QgsSettings settings;
395 QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
396 QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Color Table as File" ), lastDir, tr( "Text (*.clr)" ) );
397 if ( !fileName.isEmpty() )
398 {
399 if ( !fileName.endsWith( QLatin1String( ".clr" ), Qt::CaseInsensitive ) )
400 {
401 fileName = fileName + ".clr";
402 }
403
404 QFile outputFile( fileName );
405 if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
406 {
407 QTextStream outputStream( &outputFile );
408 outputStream << QgsPalettedRasterRenderer::classDataToString( mProxyModel->classData() );
409 outputStream.flush();
410 outputFile.close();
411
412 QFileInfo fileInfo( fileName );
413 settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
414 }
415 else
416 {
417 QMessageBox::warning( this, tr( "Save Color Table as File" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
418 }
419 }
420}
421
422void QgsPalettedRendererWidget::classify()
423{
424 if ( mRasterLayer )
425 {
427 if ( !provider )
428 {
429 return;
430 }
431
432 if ( mGatherer )
433 {
434 mGatherer->stop();
435 return;
436 }
437
438 mGatherer = new QgsPalettedRendererClassGatherer( mRasterLayer, mBandComboBox->currentBand(), mModel->classData(), btnColorRamp->colorRamp() );
439
440 connect( mGatherer, &QgsPalettedRendererClassGatherer::progressChanged, mCalculatingProgressBar, [ = ]( int progress )
441 {
442 mCalculatingProgressBar->setValue( progress );
443 } );
444
445 mCalculatingProgressBar->show();
446 mCancelButton->show();
447 connect( mCancelButton, &QPushButton::clicked, mGatherer, &QgsPalettedRendererClassGatherer::stop );
448
449 connect( mGatherer, &QgsPalettedRendererClassGatherer::collectedClasses, this, &QgsPalettedRendererWidget::gatheredClasses );
450 connect( mGatherer, &QgsPalettedRendererClassGatherer::finished, this, &QgsPalettedRendererWidget::gathererThreadFinished );
451 mClassifyButton->setText( tr( "Calculating…" ) );
452 mClassifyButton->setEnabled( false );
453 mGatherer->start();
454 }
455}
456
457void QgsPalettedRendererWidget::loadFromLayer()
458{
459 //read default palette settings from layer
461 if ( provider )
462 {
463 QList<QgsColorRampShader::ColorRampItem> table = provider->colorTable( mBandComboBox->currentBand() );
464 if ( !table.isEmpty() )
465 {
466 QgsPalettedRasterRenderer::ClassData classes = QgsPalettedRasterRenderer::colorTableToClassData( provider->colorTable( mBandComboBox->currentBand() ) );
467 mModel->setClassData( classes );
468 emit widgetChanged();
469 }
470 }
471}
472
473void QgsPalettedRendererWidget::bandChanged( int band )
474{
475 if ( band == mBand )
476 return;
477
479 {
480 mValueDelegate->setDataType( mRasterLayer->dataProvider( )->dataType( mBand ) );
481 }
482
483 bool deleteExisting = false;
484 if ( !mModel->classData().isEmpty() )
485 {
486 int res = QMessageBox::question( this,
487 tr( "Delete Classification" ),
488 tr( "The classification band was changed from %1 to %2.\n"
489 "Should the existing classes be deleted?" ).arg( mBand ).arg( band ),
490 QMessageBox::Yes | QMessageBox::No );
491
492 deleteExisting = ( res == QMessageBox::Yes );
493 }
494
495 mBand = band;
496 mModel->blockSignals( true );
497 if ( deleteExisting )
498 mModel->deleteAll();
499
500 mModel->blockSignals( false );
501 emit widgetChanged();
502}
503
504void QgsPalettedRendererWidget::gatheredClasses()
505{
506 if ( !mGatherer || mGatherer->wasCanceled() )
507 return;
508
509 mModel->setClassData( mGatherer->classes() );
510 emit widgetChanged();
511}
512
513void QgsPalettedRendererWidget::gathererThreadFinished()
514{
515 mGatherer->deleteLater();
516 mGatherer = nullptr;
517 mClassifyButton->setText( tr( "Classify" ) );
518 mClassifyButton->setEnabled( true );
519 mCalculatingProgressBar->hide();
520 mCancelButton->hide();
521}
522
523void QgsPalettedRendererWidget::layerWillBeRemoved( QgsMapLayer *layer )
524{
525 if ( mGatherer && mRasterLayer == layer )
526 {
527 mGatherer->stop();
528 mGatherer->wait();
529 }
530}
531
532//
533// QgsPalettedRendererModel
534//
535
537QgsPalettedRendererModel::QgsPalettedRendererModel( QObject *parent )
538 : QAbstractItemModel( parent )
539{
540
541}
542
543void QgsPalettedRendererModel::setClassData( const QgsPalettedRasterRenderer::ClassData &data )
544{
545 beginResetModel();
546 mData = data;
547 endResetModel();
548}
549
550QModelIndex QgsPalettedRendererModel::index( int row, int column, const QModelIndex &parent ) const
551{
552 if ( column < 0 || column >= columnCount() )
553 {
554 //column out of bounds
555 return QModelIndex();
556 }
557
558 if ( !parent.isValid() && row >= 0 && row < mData.size() )
559 {
560 //return an index for the item at this position
561 return createIndex( row, column );
562 }
563
564 //only top level supported
565 return QModelIndex();
566}
567
568QModelIndex QgsPalettedRendererModel::parent( const QModelIndex &index ) const
569{
570 Q_UNUSED( index )
571
572 //all items are top level
573 return QModelIndex();
574}
575
576int QgsPalettedRendererModel::columnCount( const QModelIndex &parent ) const
577{
578 if ( parent.isValid() )
579 return 0;
580
581 return 3;
582}
583
584int QgsPalettedRendererModel::rowCount( const QModelIndex &parent ) const
585{
586 if ( parent.isValid() )
587 return 0;
588
589 return mData.count();
590}
591
592QVariant QgsPalettedRendererModel::data( const QModelIndex &index, int role ) const
593{
594 if ( !index.isValid() )
595 return QVariant();
596
597 switch ( role )
598 {
599 case Qt::DisplayRole:
600 case Qt::EditRole:
601 {
602 switch ( index.column() )
603 {
604 case ValueColumn:
605 return mData.at( index.row() ).value;
606
607 case ColorColumn:
608 return mData.at( index.row() ).color;
609
610 case LabelColumn:
611 return mData.at( index.row() ).label;
612 }
613 }
614
615 default:
616 break;
617 }
618
619 return QVariant();
620}
621
622QVariant QgsPalettedRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
623{
624 switch ( orientation )
625 {
626 case Qt::Vertical:
627 return QVariant();
628
629 case Qt::Horizontal:
630 {
631 switch ( role )
632 {
633 case Qt::DisplayRole:
634 {
635 switch ( section )
636 {
637 case ValueColumn:
638 return tr( "Value" );
639
640 case ColorColumn:
641 return tr( "Color" );
642
643 case LabelColumn:
644 return tr( "Label" );
645 }
646 }
647
648 }
649 break;
650 }
651
652 default:
653 return QAbstractItemModel::headerData( section, orientation, role );
654 }
655 return QAbstractItemModel::headerData( section, orientation, role );
656}
657
658bool QgsPalettedRendererModel::setData( const QModelIndex &index, const QVariant &value, int )
659{
660 if ( !index.isValid() )
661 return false;
662 if ( index.row() >= mData.length() )
663 return false;
664
665 switch ( index.column() )
666 {
667 case ValueColumn:
668 {
669 bool ok = false;
670 double newValue = value.toDouble( &ok );
671 if ( !ok )
672 return false;
673
674 mData[ index.row() ].value = newValue;
675 emit dataChanged( index, index );
676 emit classesChanged();
677 return true;
678 }
679
680 case ColorColumn:
681 {
682 mData[ index.row() ].color = value.value<QColor>();
683 emit dataChanged( index, index );
684 emit classesChanged();
685 return true;
686 }
687
688 case LabelColumn:
689 {
690 mData[ index.row() ].label = value.toString();
691 emit dataChanged( index, index );
692 emit classesChanged();
693 return true;
694 }
695 }
696
697 return false;
698}
699
700Qt::ItemFlags QgsPalettedRendererModel::flags( const QModelIndex &index ) const
701{
702 if ( !index.isValid() )
703 return QAbstractItemModel::flags( index ) | Qt::ItemIsDropEnabled;
704
705 Qt::ItemFlags f = QAbstractItemModel::flags( index );
706 switch ( index.column() )
707 {
708 case ValueColumn:
709 case LabelColumn:
710 case ColorColumn:
711 f = f | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
712 break;
713 }
714 return f | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
715}
716
717bool QgsPalettedRendererModel::removeRows( int row, int count, const QModelIndex &parent )
718{
719 if ( row < 0 || row >= mData.count() )
720 return false;
721 if ( parent.isValid() )
722 return false;
723
724 for ( int i = row + count - 1; i >= row; --i )
725 {
726 beginRemoveRows( parent, i, i );
727 mData.removeAt( i );
728 endRemoveRows();
729 }
730 emit classesChanged();
731 return true;
732}
733
734bool QgsPalettedRendererModel::insertRows( int row, int count, const QModelIndex & )
735{
736 QgsPalettedRasterRenderer::ClassData::const_iterator cIt = mData.constBegin();
737 int currentMaxValue = -std::numeric_limits<int>::max();
738 for ( ; cIt != mData.constEnd(); ++cIt )
739 {
740 int value = cIt->value;
741 currentMaxValue = std::max( value, currentMaxValue );
742 }
743 int nextValue = std::max( 0, currentMaxValue + 1 );
744
745 beginInsertRows( QModelIndex(), row, row + count - 1 );
746 for ( int i = row; i < row + count; ++i, ++nextValue )
747 {
748 mData.insert( i, QgsPalettedRasterRenderer::Class( nextValue, QColor( 200, 200, 200 ), QLocale().toString( nextValue ) ) );
749 }
750 endInsertRows();
751 emit classesChanged();
752 return true;
753}
754
755Qt::DropActions QgsPalettedRendererModel::supportedDropActions() const
756{
757 return Qt::MoveAction;
758}
759
760QStringList QgsPalettedRendererModel::mimeTypes() const
761{
762 QStringList types;
763 types << QStringLiteral( "application/x-qgspalettedrenderermodel" );
764 return types;
765}
766
767QMimeData *QgsPalettedRendererModel::mimeData( const QModelIndexList &indexes ) const
768{
769 QMimeData *mimeData = new QMimeData();
770 QByteArray encodedData;
771
772 QDataStream stream( &encodedData, QIODevice::WriteOnly );
773
774 // Create list of rows
775 const auto constIndexes = indexes;
776 for ( const QModelIndex &index : constIndexes )
777 {
778 if ( !index.isValid() || index.column() != 0 )
779 continue;
780
781 stream << index.row();
782 }
783 mimeData->setData( QStringLiteral( "application/x-qgspalettedrenderermodel" ), encodedData );
784 return mimeData;
785}
786
787bool QgsPalettedRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex & )
788{
789 Q_UNUSED( column )
790 if ( action != Qt::MoveAction ) return true;
791
792 if ( !data->hasFormat( QStringLiteral( "application/x-qgspalettedrenderermodel" ) ) )
793 return false;
794
795 QByteArray encodedData = data->data( QStringLiteral( "application/x-qgspalettedrenderermodel" ) );
796 QDataStream stream( &encodedData, QIODevice::ReadOnly );
797
798 QVector<int> rows;
799 while ( !stream.atEnd() )
800 {
801 int r;
802 stream >> r;
803 rows.append( r );
804 }
805
807 for ( int i = 0; i < rows.count(); ++i )
808 newData << mData.at( rows.at( i ) );
809
810 if ( row < 0 )
811 row = mData.count();
812
813 beginInsertRows( QModelIndex(), row, row + rows.count() - 1 );
814 for ( int i = 0; i < rows.count(); ++i )
815 mData.insert( row + i, newData.at( i ) );
816 endInsertRows();
817 emit classesChanged();
818 return true;
819}
820
821QModelIndex QgsPalettedRendererModel::addEntry( const QColor &color )
822{
823 insertRow( rowCount() );
824 QModelIndex newRow = index( mData.count() - 1, 1 );
825 setData( newRow, color );
826 return newRow;
827}
828
829void QgsPalettedRendererModel::deleteAll()
830{
831 beginResetModel();
832 mData.clear();
833 endResetModel();
834 emit classesChanged();
835}
836
837//
838// QgsPalettedRendererClassGatherer
839//
840
841QgsPalettedRendererClassGatherer::QgsPalettedRendererClassGatherer( QgsRasterLayer *layer, int bandNumber, const QgsPalettedRasterRenderer::ClassData &existingClasses, QgsColorRamp *ramp )
842 : mProvider( ( layer && layer->dataProvider() ) ? layer->dataProvider()->clone() : nullptr )
843 , mBandNumber( bandNumber )
844 , mRamp( ramp )
845 , mClasses( existingClasses )
846 , mWasCanceled( false )
847{}
848
849void QgsPalettedRendererClassGatherer::run()
850{
851 mWasCanceled = false;
852
853 // allow responsive cancellation
854 mFeedback = new QgsRasterBlockFeedback();
855 connect( mFeedback, &QgsRasterBlockFeedback::progressChanged, this, &QgsPalettedRendererClassGatherer::progressChanged );
856
857 if ( mProvider )
858 {
859 QgsPalettedRasterRenderer::ClassData newClasses = QgsPalettedRasterRenderer::classDataFromRaster( mProvider.get(), mBandNumber, mRamp.get(), mFeedback );
860
861 // combine existing classes with new classes
862 QgsPalettedRasterRenderer::ClassData::iterator classIt = newClasses.begin();
863 emit progressChanged( 0 );
864 qlonglong i = 0;
865 for ( ; classIt != newClasses.end(); ++classIt )
866 {
867 // check if existing classes contains this same class
868 for ( const QgsPalettedRasterRenderer::Class &existingClass : std::as_const( mClasses ) )
869 {
870 if ( existingClass.value == classIt->value )
871 {
872 classIt->color = existingClass.color;
873 classIt->label = existingClass.label;
874 break;
875 }
876 }
877 i ++;
878 emit progressChanged( 100 * ( static_cast< double >( i ) / static_cast<double>( newClasses.count() ) ) );
879 }
880 mClasses = newClasses;
881 }
882
883 // be overly cautious - it's *possible* stop() might be called between deleting mFeedback and nulling it
884 mFeedbackMutex.lock();
885 delete mFeedback;
886 mFeedback = nullptr;
887 mFeedbackMutex.unlock();
888
889 emit collectedClasses();
890}
891
892
893QgsPalettedRasterRenderer::ClassData QgsPalettedRendererProxyModel::classData() const
894{
896 for ( int i = 0; i < rowCount( ); ++i )
897 {
898 data.push_back( qobject_cast<QgsPalettedRendererModel *>( sourceModel() )->classAtIndex( mapToSource( index( i, 0 ) ) ) );
899 }
900 return data;
901}
902
903
@ UnknownDataType
Unknown or unspecified type.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:2898
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.
Abstract base class for color ramps.
Definition: qgscolorramp.h:30
A delegate for showing a color swatch in a list.
A custom QGIS widget for selecting a color, including options for selecting colors via hue wheel,...
@ 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.
void progressChanged(double progress)
Emitted when the feedback object reports a progress change.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
Renderer for paletted raster images.
int band() const
Returns the raster band used for rendering the raster.
QgsColorRamp * sourceColorRamp() const
Gets the source color ramp.
void setSourceColorRamp(QgsColorRamp *ramp)
Set the source color ramp.
QList< QgsPalettedRasterRenderer::Class > ClassData
Map of value to class properties.
static QgsPalettedRasterRenderer::ClassData classDataFromFile(const QString &path)
Opens a color table file and returns corresponding paletted renderer class data.
static QgsPalettedRasterRenderer::ClassData colorTableToClassData(const QList< QgsColorRampShader::ColorRampItem > &table)
Converts a raster color table to paletted renderer class data.
ClassData classes() const
Returns a map of value to classes (colors) used by the renderer.
static QgsPalettedRasterRenderer::ClassData classDataFromRaster(QgsRasterInterface *raster, int bandNumber, QgsColorRamp *ramp=nullptr, QgsRasterBlockFeedback *feedback=nullptr)
Generates class data from a raster, for the specified bandNumber.
static QString classDataToString(const QgsPalettedRasterRenderer::ClassData &classes)
Converts classes to a string representation, using the .clr/gdal color table file format.
QgsRasterRenderer * renderer() override
Creates a new renderer, using the properties defined in the widget.
void setFromRenderer(const QgsRasterRenderer *r)
Sets the widget state from the specified renderer.
QgsPalettedRendererWidget(QgsRasterLayer *layer, const QgsRectangle &extent=QgsRectangle())
Base class for any widget that can be shown as a inline panel.
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.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:106
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:477
void layerWillBeRemoved(const QString &layerId)
Emitted when a layer is about to be removed from the registry.
Totally random color ramp.
void bandChanged(int band)
Emitted when the currently selected band changes.
Feedback object tailored for raster block reading.
Base class for raster data providers.
Qgis::DataType dataType(int bandNo) const override=0
Returns data type for the band specified by number.
virtual QList< QgsColorRampShader::ColorRampItem > colorTable(int bandNo) const
Represents a raster layer.
QgsRasterRenderer * renderer() const
Returns the raster's renderer.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
Abstract base class for widgets which configure a QgsRasterRenderer.
void widgetChanged()
Emitted when something on the widget has changed.
Raster renderer pipe that applies colors to a raster.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
This class is a composition of two QSettings instances:
Definition: qgssettings.h:63
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.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:3053
Properties of a single value class.