29#include <QColorDialog>
31#include <QInputDialog>
37#include "moc_qgspalettedrendererwidget.cpp"
39#ifdef ENABLE_MODELTEST
49 mCalculatingProgressBar->hide();
50 mCancelButton->hide();
52 mContextMenu =
new QMenu( tr(
"Options" ),
this );
53 mContextMenu->addAction( tr(
"Change Color…" ),
this, &QgsPalettedRendererWidget::changeColor );
54 mContextMenu->addAction( tr(
"Change Opacity…" ),
this, &QgsPalettedRendererWidget::changeOpacity );
55 mContextMenu->addAction( tr(
"Change Label…" ),
this, &QgsPalettedRendererWidget::changeLabel );
57 mAdvancedMenu =
new QMenu( tr(
"Advanced Options" ),
this );
58 QAction *mLoadFromLayerAction = mAdvancedMenu->addAction( tr(
"Load Classes from Layer" ) );
59 connect( mLoadFromLayerAction, &QAction::triggered,
this, &QgsPalettedRendererWidget::loadFromLayer );
60 QAction *loadFromFile = mAdvancedMenu->addAction( tr(
"Load Color Map from File…" ) );
61 connect( loadFromFile, &QAction::triggered,
this, &QgsPalettedRendererWidget::loadColorTable );
62 QAction *exportToFile = mAdvancedMenu->addAction( tr(
"Export Color Map to File…" ) );
63 connect( exportToFile, &QAction::triggered,
this, &QgsPalettedRendererWidget::saveColorTable );
66 mButtonAdvanced->setMenu( mAdvancedMenu );
68 mModel =
new QgsPalettedRendererModel(
this );
69 mProxyModel =
new QgsPalettedRendererProxyModel(
this );
70 mProxyModel->setSourceModel( mModel );
71 mTreeView->setSortingEnabled(
false );
72 mTreeView->setModel( mProxyModel );
75 mProxyModel->sort( QgsPalettedRendererModel::Column::ValueColumn );
78#ifdef ENABLE_MODELTEST
79 new ModelTest( mModel,
this );
82 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ColorColumn,
new QgsColorSwatchDelegate(
this ) );
84 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ValueColumn, mValueDelegate );
86 mTreeView->setColumnWidth( QgsPalettedRendererModel::ColorColumn,
Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance(
'X' ) * 6.6 );
87 mTreeView->setContextMenuPolicy( Qt::CustomContextMenu );
88 mTreeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
89 mTreeView->setDragEnabled(
true );
90 mTreeView->setAcceptDrops(
true );
91 mTreeView->setDropIndicatorShown(
true );
92 mTreeView->setDragDropMode( QAbstractItemView::InternalMove );
93 mTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
94 mTreeView->setDefaultDropAction( Qt::MoveAction );
96 connect( mTreeView, &QTreeView::customContextMenuRequested,
this, [
this]( QPoint ) { mContextMenu->exec( QCursor::pos() ); } );
98 btnColorRamp->setShowRandomColorRamp(
true );
116 connect( mDeleteEntryButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::deleteEntry );
117 connect( mButtonDeleteAll, &QPushButton::clicked, mModel, &QgsPalettedRendererModel::deleteAll );
118 connect( mAddEntryButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::addEntry );
119 connect( mClassifyButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::classify );
123 mLoadFromLayerAction->setEnabled( !
mRasterLayer->dataProvider()->colorTable( mBandComboBox->currentBand() ).isEmpty() );
127 mLoadFromLayerAction->setEnabled(
false );
146 int bandNumber = mBandComboBox->currentBand();
149 if ( !btnColorRamp->isNull() )
165 mModel->setClassData( pr->
classes() );
186 mValueDelegate->setDataType(
mRasterLayer->dataProvider()->dataType( mBand ) );
190void QgsPalettedRendererWidget::setSelectionColor(
const QItemSelection &selection,
const QColor &color )
195 QModelIndex colorIndex;
196 const auto constSelection = selection;
197 for (
const QItemSelectionRange &range : constSelection )
199 const auto constIndexes = range.indexes();
200 for (
const QModelIndex &index : constIndexes )
202 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
203 mModel->setData( colorIndex, color, Qt::EditRole );
211void QgsPalettedRendererWidget::deleteEntry()
216 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
217 const auto constSel = sel;
218 for (
const QItemSelectionRange &range : constSel )
220 if ( range.isValid() )
221 mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
229void QgsPalettedRendererWidget::addEntry()
233 QColor color( 150, 150, 150 );
234 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
237 color = ramp->color( 1.0 );
239 QModelIndex newEntry = mModel->addEntry( color );
240 mTreeView->scrollTo( newEntry );
241 mTreeView->selectionModel()->select( mProxyModel->mapFromSource( newEntry ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
246void QgsPalettedRendererWidget::changeColor()
248 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
249 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
250 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
253 if ( panel && panel->dockMode() )
259 panel->openPanel( colorWidget );
265 if ( newColor.isValid() )
267 setSelectionColor( sel, newColor );
272void QgsPalettedRendererWidget::changeOpacity()
274 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
275 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
276 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
279 double oldOpacity = ( currentColor.alpha() / 255.0 ) * 100.0;
280 double opacity = QInputDialog::getDouble(
this, tr(
"Opacity" ), tr(
"Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
283 int newOpacity = opacity / 100 * 255;
288 const auto constSel = sel;
289 for (
const QItemSelectionRange &range : constSel )
291 const auto constIndexes = range.indexes();
292 for (
const QModelIndex &index : constIndexes )
294 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
296 QColor newColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
297 newColor.setAlpha( newOpacity );
298 mModel->setData( colorIndex, newColor, Qt::EditRole );
307void QgsPalettedRendererWidget::changeLabel()
309 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
310 QModelIndex labelIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::LabelColumn );
311 QString currentLabel = mModel->data( labelIndex, Qt::DisplayRole ).toString();
314 QString newLabel = QInputDialog::getText(
this, tr(
"Label" ), tr(
"Change label" ), QLineEdit::Normal, currentLabel, &ok );
320 const auto constSel = sel;
321 for (
const QItemSelectionRange &range : constSel )
323 const auto constIndexes = range.indexes();
324 for (
const QModelIndex &index : constIndexes )
326 labelIndex = mModel->index( index.row(), QgsPalettedRendererModel::LabelColumn );
327 mModel->setData( labelIndex, newLabel, Qt::EditRole );
336void QgsPalettedRendererWidget::applyColorRamp()
338 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
347 QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
349 double numberOfEntries = data.count();
352 if ( QgsRandomColorRamp *randomRamp =
dynamic_cast<QgsRandomColorRamp *
>( ramp.get() ) )
356 randomRamp->setTotalColorCount( numberOfEntries );
359 if ( numberOfEntries > 1 )
360 numberOfEntries -= 1;
362 for ( ; cIt != data.end(); ++cIt )
364 cIt->color = ramp->color( i / numberOfEntries );
367 mModel->setClassData( data );
373void QgsPalettedRendererWidget::loadColorTable()
375 QgsSettings settings;
376 QString lastDir = settings.
value( QStringLiteral(
"lastColorMapDir" ), QDir::homePath() ).toString();
377 QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Load Color Table from File" ), lastDir );
378 if ( !fileName.isEmpty() )
381 if ( !classes.isEmpty() )
384 mModel->setClassData( classes );
390 QMessageBox::critical(
nullptr, tr(
"Load Color Table" ), tr(
"Could not interpret file as a raster color table." ) );
395void QgsPalettedRendererWidget::saveColorTable()
397 QgsSettings settings;
398 QString lastDir = settings.
value( QStringLiteral(
"lastColorMapDir" ), QDir::homePath() ).toString();
399 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Color Table as File" ), lastDir, tr(
"Text (*.clr)" ) );
400 if ( !fileName.isEmpty() )
402 if ( !fileName.endsWith( QLatin1String(
".clr" ), Qt::CaseInsensitive ) )
404 fileName = fileName +
".clr";
407 QFile outputFile( fileName );
408 if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
410 QTextStream outputStream( &outputFile );
412 outputStream.flush();
415 QFileInfo fileInfo( fileName );
416 settings.
setValue( QStringLiteral(
"lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
420 QMessageBox::warning(
this, tr(
"Save Color Table as File" ), tr(
"Write access denied. Adjust the file permissions and try again.\n\n" ) );
425void QgsPalettedRendererWidget::classify()
429 QgsRasterDataProvider *provider =
mRasterLayer->dataProvider();
441 mGatherer =
new QgsPalettedRendererClassGatherer(
mRasterLayer, mBandComboBox->currentBand(), mModel->classData(), btnColorRamp->colorRamp() );
443 connect( mGatherer, &QgsPalettedRendererClassGatherer::progressChanged, mCalculatingProgressBar, [
this](
int progress ) {
444 mCalculatingProgressBar->setValue( progress );
447 mCalculatingProgressBar->show();
448 mCancelButton->show();
449 connect( mCancelButton, &QPushButton::clicked, mGatherer, &QgsPalettedRendererClassGatherer::stop );
451 connect( mGatherer, &QgsPalettedRendererClassGatherer::collectedClasses,
this, &QgsPalettedRendererWidget::gatheredClasses );
452 connect( mGatherer, &QgsPalettedRendererClassGatherer::finished,
this, &QgsPalettedRendererWidget::gathererThreadFinished );
453 mClassifyButton->setText( tr(
"Calculating…" ) );
454 mClassifyButton->setEnabled(
false );
459void QgsPalettedRendererWidget::loadFromLayer()
462 QgsRasterDataProvider *provider =
mRasterLayer->dataProvider();
465 QList<QgsColorRampShader::ColorRampItem> table = provider->
colorTable( mBandComboBox->currentBand() );
466 if ( !table.isEmpty() )
469 mModel->setClassData( classes );
475void QgsPalettedRendererWidget::bandChanged(
int band )
482 mValueDelegate->setDataType(
mRasterLayer->dataProvider()->dataType( mBand ) );
485 bool deleteExisting =
false;
486 if ( !mModel->classData().isEmpty() )
488 int res = QMessageBox::question(
this, tr(
"Delete Classification" ), tr(
"The classification band was changed from %1 to %2.\n"
489 "Should the existing classes be deleted?" )
492 QMessageBox::Yes | QMessageBox::No );
494 deleteExisting = ( res == QMessageBox::Yes );
498 mModel->blockSignals(
true );
499 if ( deleteExisting )
502 mModel->blockSignals(
false );
506void QgsPalettedRendererWidget::gatheredClasses()
508 if ( !mGatherer || mGatherer->wasCanceled() )
511 mModel->setClassData( mGatherer->classes() );
515void QgsPalettedRendererWidget::gathererThreadFinished()
517 mGatherer->deleteLater();
519 mClassifyButton->setText( tr(
"Classify" ) );
520 mClassifyButton->setEnabled(
true );
521 mCalculatingProgressBar->hide();
522 mCancelButton->hide();
525void QgsPalettedRendererWidget::layerWillBeRemoved(
QgsMapLayer *layer )
539QgsPalettedRendererModel::QgsPalettedRendererModel( QObject *parent )
540 : QAbstractItemModel( parent )
551QModelIndex QgsPalettedRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const
553 if ( column < 0 || column >= columnCount() )
556 return QModelIndex();
559 if ( !parent.isValid() && row >= 0 && row < mData.size() )
562 return createIndex( row, column );
566 return QModelIndex();
569QModelIndex QgsPalettedRendererModel::parent(
const QModelIndex &index )
const
574 return QModelIndex();
577int QgsPalettedRendererModel::columnCount(
const QModelIndex &parent )
const
579 if ( parent.isValid() )
585int QgsPalettedRendererModel::rowCount(
const QModelIndex &parent )
const
587 if ( parent.isValid() )
590 return mData.count();
593QVariant QgsPalettedRendererModel::data(
const QModelIndex &index,
int role )
const
595 if ( !index.isValid() )
600 case Qt::DisplayRole:
603 switch ( index.column() )
606 return mData.at( index.row() ).value;
609 return mData.at( index.row() ).color;
612 return mData.at( index.row() ).label;
623QVariant QgsPalettedRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
625 switch ( orientation )
634 case Qt::DisplayRole:
639 return tr(
"Value" );
642 return tr(
"Color" );
645 return tr(
"Label" );
653 return QAbstractItemModel::headerData( section, orientation, role );
655 return QAbstractItemModel::headerData( section, orientation, role );
658bool QgsPalettedRendererModel::setData(
const QModelIndex &index,
const QVariant &value,
int )
660 if ( !index.isValid() )
662 if ( index.row() >= mData.length() )
665 switch ( index.column() )
670 double newValue = value.toDouble( &ok );
674 mData[index.row()].value = newValue;
675 emit dataChanged( index, index );
676 emit classesChanged();
682 mData[index.row()].color = value.value<QColor>();
683 emit dataChanged( index, index );
684 emit classesChanged();
690 mData[index.row()].label = value.toString();
691 emit dataChanged( index, index );
692 emit classesChanged();
700Qt::ItemFlags QgsPalettedRendererModel::flags(
const QModelIndex &index )
const
702 if ( !index.isValid() )
703 return QAbstractItemModel::flags( index ) | Qt::ItemIsDropEnabled;
705 Qt::ItemFlags f = QAbstractItemModel::flags( index );
706 switch ( index.column() )
711 f = f | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
714 return f | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
717bool QgsPalettedRendererModel::removeRows(
int row,
int count,
const QModelIndex &parent )
719 if ( row < 0 || row >= mData.count() )
721 if ( parent.isValid() )
724 for (
int i = row + count - 1; i >= row; --i )
726 beginRemoveRows( parent, i, i );
730 emit classesChanged();
734bool QgsPalettedRendererModel::insertRows(
int row,
int count,
const QModelIndex & )
736 QgsPalettedRasterRenderer::ClassData::const_iterator cIt = mData.constBegin();
737 int currentMaxValue = -std::numeric_limits<int>::max();
738 for ( ; cIt != mData.constEnd(); ++cIt )
740 int value = cIt->value;
741 currentMaxValue = std::max( value, currentMaxValue );
743 int nextValue = std::max( 0, currentMaxValue + 1 );
745 beginInsertRows( QModelIndex(), row, row + count - 1 );
746 for (
int i = row; i < row + count; ++i, ++nextValue )
751 emit classesChanged();
755Qt::DropActions QgsPalettedRendererModel::supportedDropActions()
const
757 return Qt::MoveAction;
760QStringList QgsPalettedRendererModel::mimeTypes()
const
763 types << QStringLiteral(
"application/x-qgspalettedrenderermodel" );
767QMimeData *QgsPalettedRendererModel::mimeData(
const QModelIndexList &indexes )
const
769 QMimeData *mimeData =
new QMimeData();
770 QByteArray encodedData;
772 QDataStream stream( &encodedData, QIODevice::WriteOnly );
775 const auto constIndexes = indexes;
776 for (
const QModelIndex &index : constIndexes )
778 if ( !index.isValid() || index.column() != 0 )
781 stream << index.row();
783 mimeData->setData( QStringLiteral(
"application/x-qgspalettedrenderermodel" ), encodedData );
787bool QgsPalettedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex & )
790 if ( action != Qt::MoveAction )
793 if ( !data->hasFormat( QStringLiteral(
"application/x-qgspalettedrenderermodel" ) ) )
796 QByteArray encodedData = data->data( QStringLiteral(
"application/x-qgspalettedrenderermodel" ) );
797 QDataStream stream( &encodedData, QIODevice::ReadOnly );
800 while ( !stream.atEnd() )
808 for (
int i = 0; i < rows.count(); ++i )
809 newData << mData.at( rows.at( i ) );
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 ) );
818 emit classesChanged();
822QModelIndex QgsPalettedRendererModel::addEntry(
const QColor &color )
824 insertRow( rowCount() );
825 QModelIndex newRow = index( mData.count() - 1, 1 );
826 setData( newRow, color );
830void QgsPalettedRendererModel::deleteAll()
835 emit classesChanged();
843 : mProvider( ( layer && layer->dataProvider() ) ? layer->dataProvider()->clone() : nullptr )
844 , mBandNumber( bandNumber )
846 , mClasses( existingClasses )
850void QgsPalettedRendererClassGatherer::run()
852 mWasCanceled =
false;
863 QgsPalettedRasterRenderer::ClassData::iterator classIt = newClasses.begin();
864 emit progressChanged( 0 );
866 for ( ; classIt != newClasses.end(); ++classIt )
871 if ( existingClass.value == classIt->value )
873 classIt->color = existingClass.color;
874 classIt->label = existingClass.label;
879 emit progressChanged( 100 * (
static_cast<double>( i ) /
static_cast<double>( newClasses.count() ) ) );
881 mClasses = newClasses;
885 mFeedbackMutex.lock();
888 mFeedbackMutex.unlock();
890 emit collectedClasses();
897 for (
int i = 0; i < rowCount(); ++i )
899 data.push_back( qobject_cast<QgsPalettedRendererModel *>( sourceModel() )->classAtIndex( mapToSource( index( i, 0 ) ) ) );
@ UnknownDataType
Unknown or unspecified type.
static const double UI_SCALE_FACTOR
UI scaling factor.
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), bool allowOpacity=false)
Returns a color selection from a color dialog.
Abstract base class for color ramps.
A delegate for showing a color swatch in a list.
void progressChanged(double progress)
Emitted when the feedback object reports a progress change.
Base class for all map layer types.
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.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
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.
A color ramp consisting of random colors, constrained within component ranges.
void bandChanged(int band)
Emitted when the currently selected band changes.
Feedback object tailored for raster block reading.
Base class for raster data providers.
virtual QList< QgsColorRampShader::ColorRampItem > colorTable(int bandNo) const
Represents a raster layer.
Raster renderer pipe that applies colors to a raster.
A rectangle specified with double values.
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.
Properties of a single value class.