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