QGIS API Documentation 3.41.0-Master (cea29feecf2)
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 : QgsRasterRendererWidget( layer, extent )
44{
45 setupUi( this );
46
47 mCalculatingProgressBar->hide();
48 mCancelButton->hide();
49
50 mContextMenu = new QMenu( tr( "Options" ), this );
51 mContextMenu->addAction( tr( "Change Color…" ), this, SLOT( changeColor() ) );
52 mContextMenu->addAction( tr( "Change Opacity…" ), this, SLOT( changeOpacity() ) );
53 mContextMenu->addAction( tr( "Change Label…" ), this, SLOT( changeLabel() ) );
54
55 mAdvancedMenu = new QMenu( tr( "Advanced Options" ), this );
56 QAction *mLoadFromLayerAction = mAdvancedMenu->addAction( tr( "Load Classes from Layer" ) );
57 connect( mLoadFromLayerAction, &QAction::triggered, this, &QgsPalettedRendererWidget::loadFromLayer );
58 QAction *loadFromFile = mAdvancedMenu->addAction( tr( "Load Color Map from File…" ) );
59 connect( loadFromFile, &QAction::triggered, this, &QgsPalettedRendererWidget::loadColorTable );
60 QAction *exportToFile = mAdvancedMenu->addAction( tr( "Export Color Map to File…" ) );
61 connect( exportToFile, &QAction::triggered, this, &QgsPalettedRendererWidget::saveColorTable );
62
63
64 mButtonAdvanced->setMenu( mAdvancedMenu );
65
66 mModel = new QgsPalettedRendererModel( this );
67 mProxyModel = new QgsPalettedRendererProxyModel( this );
68 mProxyModel->setSourceModel( mModel );
69 mTreeView->setSortingEnabled( false );
70 mTreeView->setModel( mProxyModel );
71
72 connect( this, &QgsPalettedRendererWidget::widgetChanged, this, [=] {
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 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, tr( "Delete Classification" ), tr( "The classification band was changed from %1 to %2.\n"
487 "Should the existing classes be deleted?" )
488 .arg( mBand )
489 .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
542void QgsPalettedRendererModel::setClassData( const QgsPalettedRasterRenderer::ClassData &data )
543{
544 beginResetModel();
545 mData = data;
546 endResetModel();
547}
548
549QModelIndex QgsPalettedRendererModel::index( int row, int column, const QModelIndex &parent ) const
550{
551 if ( column < 0 || column >= columnCount() )
552 {
553 //column out of bounds
554 return QModelIndex();
555 }
556
557 if ( !parent.isValid() && row >= 0 && row < mData.size() )
558 {
559 //return an index for the item at this position
560 return createIndex( row, column );
561 }
562
563 //only top level supported
564 return QModelIndex();
565}
566
567QModelIndex QgsPalettedRendererModel::parent( const QModelIndex &index ) const
568{
569 Q_UNUSED( index )
570
571 //all items are top level
572 return QModelIndex();
573}
574
575int QgsPalettedRendererModel::columnCount( const QModelIndex &parent ) const
576{
577 if ( parent.isValid() )
578 return 0;
579
580 return 3;
581}
582
583int QgsPalettedRendererModel::rowCount( const QModelIndex &parent ) const
584{
585 if ( parent.isValid() )
586 return 0;
587
588 return mData.count();
589}
590
591QVariant QgsPalettedRendererModel::data( const QModelIndex &index, int role ) const
592{
593 if ( !index.isValid() )
594 return QVariant();
595
596 switch ( role )
597 {
598 case Qt::DisplayRole:
599 case Qt::EditRole:
600 {
601 switch ( index.column() )
602 {
603 case ValueColumn:
604 return mData.at( index.row() ).value;
605
606 case ColorColumn:
607 return mData.at( index.row() ).color;
608
609 case LabelColumn:
610 return mData.at( index.row() ).label;
611 }
612 }
613
614 default:
615 break;
616 }
617
618 return QVariant();
619}
620
621QVariant QgsPalettedRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
622{
623 switch ( orientation )
624 {
625 case Qt::Vertical:
626 return QVariant();
627
628 case Qt::Horizontal:
629 {
630 switch ( role )
631 {
632 case Qt::DisplayRole:
633 {
634 switch ( section )
635 {
636 case ValueColumn:
637 return tr( "Value" );
638
639 case ColorColumn:
640 return tr( "Color" );
641
642 case LabelColumn:
643 return tr( "Label" );
644 }
645 }
646 }
647 break;
648 }
649
650 default:
651 return QAbstractItemModel::headerData( section, orientation, role );
652 }
653 return QAbstractItemModel::headerData( section, orientation, role );
654}
655
656bool QgsPalettedRendererModel::setData( const QModelIndex &index, const QVariant &value, int )
657{
658 if ( !index.isValid() )
659 return false;
660 if ( index.row() >= mData.length() )
661 return false;
662
663 switch ( index.column() )
664 {
665 case ValueColumn:
666 {
667 bool ok = false;
668 double newValue = value.toDouble( &ok );
669 if ( !ok )
670 return false;
671
672 mData[index.row()].value = newValue;
673 emit dataChanged( index, index );
674 emit classesChanged();
675 return true;
676 }
677
678 case ColorColumn:
679 {
680 mData[index.row()].color = value.value<QColor>();
681 emit dataChanged( index, index );
682 emit classesChanged();
683 return true;
684 }
685
686 case LabelColumn:
687 {
688 mData[index.row()].label = value.toString();
689 emit dataChanged( index, index );
690 emit classesChanged();
691 return true;
692 }
693 }
694
695 return false;
696}
697
698Qt::ItemFlags QgsPalettedRendererModel::flags( const QModelIndex &index ) const
699{
700 if ( !index.isValid() )
701 return QAbstractItemModel::flags( index ) | Qt::ItemIsDropEnabled;
702
703 Qt::ItemFlags f = QAbstractItemModel::flags( index );
704 switch ( index.column() )
705 {
706 case ValueColumn:
707 case LabelColumn:
708 case ColorColumn:
709 f = f | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
710 break;
711 }
712 return f | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
713}
714
715bool QgsPalettedRendererModel::removeRows( int row, int count, const QModelIndex &parent )
716{
717 if ( row < 0 || row >= mData.count() )
718 return false;
719 if ( parent.isValid() )
720 return false;
721
722 for ( int i = row + count - 1; i >= row; --i )
723 {
724 beginRemoveRows( parent, i, i );
725 mData.removeAt( i );
726 endRemoveRows();
727 }
728 emit classesChanged();
729 return true;
730}
731
732bool QgsPalettedRendererModel::insertRows( int row, int count, const QModelIndex & )
733{
734 QgsPalettedRasterRenderer::ClassData::const_iterator cIt = mData.constBegin();
735 int currentMaxValue = -std::numeric_limits<int>::max();
736 for ( ; cIt != mData.constEnd(); ++cIt )
737 {
738 int value = cIt->value;
739 currentMaxValue = std::max( value, currentMaxValue );
740 }
741 int nextValue = std::max( 0, currentMaxValue + 1 );
742
743 beginInsertRows( QModelIndex(), row, row + count - 1 );
744 for ( int i = row; i < row + count; ++i, ++nextValue )
745 {
746 mData.insert( i, QgsPalettedRasterRenderer::Class( nextValue, QColor( 200, 200, 200 ), QLocale().toString( nextValue ) ) );
747 }
748 endInsertRows();
749 emit classesChanged();
750 return true;
751}
752
753Qt::DropActions QgsPalettedRendererModel::supportedDropActions() const
754{
755 return Qt::MoveAction;
756}
757
758QStringList QgsPalettedRendererModel::mimeTypes() const
759{
760 QStringList types;
761 types << QStringLiteral( "application/x-qgspalettedrenderermodel" );
762 return types;
763}
764
765QMimeData *QgsPalettedRendererModel::mimeData( const QModelIndexList &indexes ) const
766{
767 QMimeData *mimeData = new QMimeData();
768 QByteArray encodedData;
769
770 QDataStream stream( &encodedData, QIODevice::WriteOnly );
771
772 // Create list of rows
773 const auto constIndexes = indexes;
774 for ( const QModelIndex &index : constIndexes )
775 {
776 if ( !index.isValid() || index.column() != 0 )
777 continue;
778
779 stream << index.row();
780 }
781 mimeData->setData( QStringLiteral( "application/x-qgspalettedrenderermodel" ), encodedData );
782 return mimeData;
783}
784
785bool QgsPalettedRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex & )
786{
787 Q_UNUSED( column )
788 if ( action != Qt::MoveAction )
789 return true;
790
791 if ( !data->hasFormat( QStringLiteral( "application/x-qgspalettedrenderermodel" ) ) )
792 return false;
793
794 QByteArray encodedData = data->data( QStringLiteral( "application/x-qgspalettedrenderermodel" ) );
795 QDataStream stream( &encodedData, QIODevice::ReadOnly );
796
797 QVector<int> rows;
798 while ( !stream.atEnd() )
799 {
800 int r;
801 stream >> r;
802 rows.append( r );
803 }
804
806 for ( int i = 0; i < rows.count(); ++i )
807 newData << mData.at( rows.at( i ) );
808
809 if ( row < 0 )
810 row = mData.count();
811
812 beginInsertRows( QModelIndex(), row, row + rows.count() - 1 );
813 for ( int i = 0; i < rows.count(); ++i )
814 mData.insert( row + i, newData.at( i ) );
815 endInsertRows();
816 emit classesChanged();
817 return true;
818}
819
820QModelIndex QgsPalettedRendererModel::addEntry( const QColor &color )
821{
822 insertRow( rowCount() );
823 QModelIndex newRow = index( mData.count() - 1, 1 );
824 setData( newRow, color );
825 return newRow;
826}
827
828void QgsPalettedRendererModel::deleteAll()
829{
830 beginResetModel();
831 mData.clear();
832 endResetModel();
833 emit classesChanged();
834}
835
836//
837// QgsPalettedRendererClassGatherer
838//
839
840QgsPalettedRendererClassGatherer::QgsPalettedRendererClassGatherer( QgsRasterLayer *layer, int bandNumber, const QgsPalettedRasterRenderer::ClassData &existingClasses, QgsColorRamp *ramp )
841 : mProvider( ( layer && layer->dataProvider() ) ? layer->dataProvider()->clone() : nullptr )
842 , mBandNumber( bandNumber )
843 , mRamp( ramp )
844 , mClasses( existingClasses )
845 , mWasCanceled( false )
846{}
847
848void QgsPalettedRendererClassGatherer::run()
849{
850 mWasCanceled = false;
851
852 // allow responsive cancellation
853 mFeedback = new QgsRasterBlockFeedback();
854 connect( mFeedback, &QgsRasterBlockFeedback::progressChanged, this, &QgsPalettedRendererClassGatherer::progressChanged );
855
856 if ( mProvider )
857 {
858 QgsPalettedRasterRenderer::ClassData newClasses = QgsPalettedRasterRenderer::classDataFromRaster( mProvider.get(), mBandNumber, mRamp.get(), mFeedback );
859
860 // combine existing classes with new classes
861 QgsPalettedRasterRenderer::ClassData::iterator classIt = newClasses.begin();
862 emit progressChanged( 0 );
863 qlonglong i = 0;
864 for ( ; classIt != newClasses.end(); ++classIt )
865 {
866 // check if existing classes contains this same class
867 for ( const QgsPalettedRasterRenderer::Class &existingClass : std::as_const( mClasses ) )
868 {
869 if ( existingClass.value == classIt->value )
870 {
871 classIt->color = existingClass.color;
872 classIt->label = existingClass.label;
873 break;
874 }
875 }
876 i++;
877 emit progressChanged( 100 * ( static_cast<double>( i ) / static_cast<double>( newClasses.count() ) ) );
878 }
879 mClasses = newClasses;
880 }
881
882 // be overly cautious - it's *possible* stop() might be called between deleting mFeedback and nulling it
883 mFeedbackMutex.lock();
884 delete mFeedback;
885 mFeedback = nullptr;
886 mFeedbackMutex.unlock();
887
888 emit collectedClasses();
889}
890
891
892QgsPalettedRasterRenderer::ClassData QgsPalettedRendererProxyModel::classData() const
893{
895 for ( int i = 0; i < rowCount(); ++i )
896 {
897 data.push_back( qobject_cast<QgsPalettedRendererModel *>( sourceModel() )->classAtIndex( mapToSource( index( i, 0 ) ) ) );
898 }
899 return data;
900}
901
902
@ UnknownDataType
Unknown or unspecified type.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:5733
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:5928
Properties of a single value class.