29#include <QColorDialog>
31#include <QInputDialog>
38#include "moc_qgspalettedrendererwidget.cpp"
40using namespace Qt::StringLiterals;
42#ifdef ENABLE_MODELTEST
52 mCalculatingProgressBar->hide();
53 mCancelButton->hide();
55 mContextMenu =
new QMenu( tr(
"Options" ),
this );
56 mContextMenu->addAction( tr(
"Change Color…" ),
this, &QgsPalettedRendererWidget::changeColor );
57 mContextMenu->addAction( tr(
"Change Opacity…" ),
this, &QgsPalettedRendererWidget::changeOpacity );
58 mContextMenu->addAction( tr(
"Change Label…" ),
this, &QgsPalettedRendererWidget::changeLabel );
60 mAdvancedMenu =
new QMenu( tr(
"Advanced Options" ),
this );
61 QAction *mLoadFromLayerAction = mAdvancedMenu->addAction( tr(
"Load Classes from Layer" ) );
62 connect( mLoadFromLayerAction, &QAction::triggered,
this, &QgsPalettedRendererWidget::loadFromLayer );
63 QAction *loadFromFile = mAdvancedMenu->addAction( tr(
"Load Color Map from File…" ) );
64 connect( loadFromFile, &QAction::triggered,
this, &QgsPalettedRendererWidget::loadColorTable );
65 QAction *exportToFile = mAdvancedMenu->addAction( tr(
"Export Color Map to File…" ) );
66 connect( exportToFile, &QAction::triggered,
this, &QgsPalettedRendererWidget::saveColorTable );
69 mButtonAdvanced->setMenu( mAdvancedMenu );
71 mModel =
new QgsPalettedRendererModel(
this );
72 mProxyModel =
new QgsPalettedRendererProxyModel(
this );
73 mProxyModel->setSourceModel( mModel );
74 mTreeView->setSortingEnabled(
false );
75 mTreeView->setModel( mProxyModel );
78 mProxyModel->sort( QgsPalettedRendererModel::Column::ValueColumn );
81#ifdef ENABLE_MODELTEST
82 new ModelTest( mModel,
this );
85 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ColorColumn,
new QgsColorSwatchDelegate(
this ) );
87 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ValueColumn, mValueDelegate );
89 mTreeView->setColumnWidth( QgsPalettedRendererModel::ColorColumn,
Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance(
'X' ) * 6.6 );
90 mTreeView->setContextMenuPolicy( Qt::CustomContextMenu );
91 mTreeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
92 mTreeView->setDragEnabled(
true );
93 mTreeView->setAcceptDrops(
true );
94 mTreeView->setDropIndicatorShown(
true );
95 mTreeView->setDragDropMode( QAbstractItemView::InternalMove );
96 mTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
97 mTreeView->setDefaultDropAction( Qt::MoveAction );
99 connect( mTreeView, &QTreeView::customContextMenuRequested,
this, [
this]( QPoint ) { mContextMenu->exec( QCursor::pos() ); } );
101 btnColorRamp->setShowRandomColorRamp(
true );
119 connect( mDeleteEntryButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::deleteEntry );
120 connect( mButtonDeleteAll, &QPushButton::clicked, mModel, &QgsPalettedRendererModel::deleteAll );
121 connect( mAddEntryButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::addEntry );
122 connect( mClassifyButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::classify );
126 mLoadFromLayerAction->setEnabled( !
mRasterLayer->dataProvider()->colorTable( mBandComboBox->currentBand() ).isEmpty() );
130 mLoadFromLayerAction->setEnabled(
false );
149 int bandNumber = mBandComboBox->currentBand();
152 if ( !btnColorRamp->isNull() )
168 mModel->setClassData( pr->
classes() );
189 mValueDelegate->setDataType(
mRasterLayer->dataProvider()->dataType( mBand ) );
193void QgsPalettedRendererWidget::setSelectionColor(
const QItemSelection &selection,
const QColor &color )
198 QModelIndex colorIndex;
199 const auto constSelection = selection;
200 for (
const QItemSelectionRange &range : constSelection )
202 const auto constIndexes = range.indexes();
203 for (
const QModelIndex &index : constIndexes )
205 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
206 mModel->setData( colorIndex, color, Qt::EditRole );
214void QgsPalettedRendererWidget::deleteEntry()
219 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
220 const auto constSel = sel;
221 for (
const QItemSelectionRange &range : constSel )
223 if ( range.isValid() )
224 mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
232void QgsPalettedRendererWidget::addEntry()
236 QColor color( 150, 150, 150 );
237 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
240 color = ramp->color( 1.0 );
242 QModelIndex newEntry = mModel->addEntry( color );
243 mTreeView->scrollTo( newEntry );
244 mTreeView->selectionModel()->select( mProxyModel->mapFromSource( newEntry ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
249void QgsPalettedRendererWidget::changeColor()
251 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
252 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
253 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
256 if ( panel && panel->dockMode() )
262 panel->openPanel( colorWidget );
268 if ( newColor.isValid() )
270 setSelectionColor( sel, newColor );
275void QgsPalettedRendererWidget::changeOpacity()
277 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
278 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
279 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
282 double oldOpacity = ( currentColor.alpha() / 255.0 ) * 100.0;
283 double opacity = QInputDialog::getDouble(
this, tr(
"Opacity" ), tr(
"Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
286 int newOpacity = opacity / 100 * 255;
291 const auto constSel = sel;
292 for (
const QItemSelectionRange &range : constSel )
294 const auto constIndexes = range.indexes();
295 for (
const QModelIndex &index : constIndexes )
297 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
299 QColor newColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
300 newColor.setAlpha( newOpacity );
301 mModel->setData( colorIndex, newColor, Qt::EditRole );
310void QgsPalettedRendererWidget::changeLabel()
312 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
313 QModelIndex labelIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::LabelColumn );
314 QString currentLabel = mModel->data( labelIndex, Qt::DisplayRole ).toString();
317 QString newLabel = QInputDialog::getText(
this, tr(
"Label" ), tr(
"Change label" ), QLineEdit::Normal, currentLabel, &ok );
323 const auto constSel = sel;
324 for (
const QItemSelectionRange &range : constSel )
326 const auto constIndexes = range.indexes();
327 for (
const QModelIndex &index : constIndexes )
329 labelIndex = mModel->index( index.row(), QgsPalettedRendererModel::LabelColumn );
330 mModel->setData( labelIndex, newLabel, Qt::EditRole );
339void QgsPalettedRendererWidget::applyColorRamp()
341 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
350 QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
352 double numberOfEntries = data.count();
355 if ( QgsRandomColorRamp *randomRamp =
dynamic_cast<QgsRandomColorRamp *
>( ramp.get() ) )
359 randomRamp->setTotalColorCount( numberOfEntries );
362 if ( numberOfEntries > 1 )
363 numberOfEntries -= 1;
365 for ( ; cIt != data.end(); ++cIt )
367 cIt->color = ramp->color( i / numberOfEntries );
370 mModel->setClassData( data );
376void QgsPalettedRendererWidget::loadColorTable()
378 QgsSettings settings;
379 QString lastDir = settings.
value( u
"lastColorMapDir"_s, QDir::homePath() ).toString();
380 QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Load Color Table from File" ), lastDir );
381 if ( !fileName.isEmpty() )
384 if ( !classes.isEmpty() )
387 mModel->setClassData( classes );
393 QMessageBox::critical(
nullptr, tr(
"Load Color Table" ), tr(
"Could not interpret file as a raster color table." ) );
398void QgsPalettedRendererWidget::saveColorTable()
400 QgsSettings settings;
401 QString lastDir = settings.
value( u
"lastColorMapDir"_s, QDir::homePath() ).toString();
402 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Color Table as File" ), lastDir, tr(
"Text (*.clr)" ) );
403 if ( !fileName.isEmpty() )
405 if ( !fileName.endsWith(
".clr"_L1, Qt::CaseInsensitive ) )
407 fileName = fileName +
".clr";
410 QFile outputFile( fileName );
411 if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
413 QTextStream outputStream( &outputFile );
415 outputStream.flush();
418 QFileInfo fileInfo( fileName );
419 settings.
setValue( u
"lastColorMapDir"_s, fileInfo.absoluteDir().absolutePath() );
423 QMessageBox::warning(
this, tr(
"Save Color Table as File" ), tr(
"Write access denied. Adjust the file permissions and try again.\n\n" ) );
428void QgsPalettedRendererWidget::classify()
432 QgsRasterDataProvider *provider =
mRasterLayer->dataProvider();
444 mGatherer =
new QgsPalettedRendererClassGatherer(
mRasterLayer, mBandComboBox->currentBand(), mModel->classData(), btnColorRamp->colorRamp() );
446 connect( mGatherer, &QgsPalettedRendererClassGatherer::progressChanged, mCalculatingProgressBar, [
this](
int progress ) {
447 mCalculatingProgressBar->setValue( progress );
450 mCalculatingProgressBar->show();
451 mCancelButton->show();
452 connect( mCancelButton, &QPushButton::clicked, mGatherer, &QgsPalettedRendererClassGatherer::stop );
454 connect( mGatherer, &QgsPalettedRendererClassGatherer::collectedClasses,
this, &QgsPalettedRendererWidget::gatheredClasses );
455 connect( mGatherer, &QgsPalettedRendererClassGatherer::finished,
this, &QgsPalettedRendererWidget::gathererThreadFinished );
456 mClassifyButton->setText( tr(
"Calculating…" ) );
457 mClassifyButton->setEnabled(
false );
462void QgsPalettedRendererWidget::loadFromLayer()
465 QgsRasterDataProvider *provider =
mRasterLayer->dataProvider();
468 QList<QgsColorRampShader::ColorRampItem> table = provider->
colorTable( mBandComboBox->currentBand() );
469 if ( !table.isEmpty() )
472 mModel->setClassData( classes );
478void QgsPalettedRendererWidget::bandChanged(
int band )
485 mValueDelegate->setDataType(
mRasterLayer->dataProvider()->dataType( mBand ) );
488 bool deleteExisting =
false;
489 if ( !mModel->classData().isEmpty() )
491 int res = QMessageBox::question(
this, tr(
"Delete Classification" ), tr(
"The classification band was changed from %1 to %2.\n"
492 "Should the existing classes be deleted?" )
495 QMessageBox::Yes | QMessageBox::No );
497 deleteExisting = ( res == QMessageBox::Yes );
501 mModel->blockSignals(
true );
502 if ( deleteExisting )
505 mModel->blockSignals(
false );
509void QgsPalettedRendererWidget::gatheredClasses()
511 if ( !mGatherer || mGatherer->wasCanceled() )
514 mModel->setClassData( mGatherer->classes() );
518void QgsPalettedRendererWidget::gathererThreadFinished()
520 mGatherer->deleteLater();
522 mClassifyButton->setText( tr(
"Classify" ) );
523 mClassifyButton->setEnabled(
true );
524 mCalculatingProgressBar->hide();
525 mCancelButton->hide();
528void QgsPalettedRendererWidget::layerWillBeRemoved(
QgsMapLayer *layer )
542QgsPalettedRendererModel::QgsPalettedRendererModel( QObject *parent )
543 : QAbstractItemModel( parent )
554QModelIndex QgsPalettedRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const
556 if ( column < 0 || column >= columnCount() )
559 return QModelIndex();
562 if ( !parent.isValid() && row >= 0 && row < mData.size() )
565 return createIndex( row, column );
569 return QModelIndex();
572QModelIndex QgsPalettedRendererModel::parent(
const QModelIndex &index )
const
577 return QModelIndex();
580int QgsPalettedRendererModel::columnCount(
const QModelIndex &parent )
const
582 if ( parent.isValid() )
588int QgsPalettedRendererModel::rowCount(
const QModelIndex &parent )
const
590 if ( parent.isValid() )
593 return mData.count();
596QVariant QgsPalettedRendererModel::data(
const QModelIndex &index,
int role )
const
598 if ( !index.isValid() )
603 case Qt::DisplayRole:
606 switch ( index.column() )
609 return mData.at( index.row() ).value;
612 return mData.at( index.row() ).color;
615 return mData.at( index.row() ).label;
626QVariant QgsPalettedRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
628 switch ( orientation )
637 case Qt::DisplayRole:
642 return tr(
"Value" );
645 return tr(
"Color" );
648 return tr(
"Label" );
656 return QAbstractItemModel::headerData( section, orientation, role );
658 return QAbstractItemModel::headerData( section, orientation, role );
661bool QgsPalettedRendererModel::setData(
const QModelIndex &index,
const QVariant &value,
int )
663 if ( !index.isValid() )
665 if ( index.row() >= mData.length() )
668 switch ( index.column() )
673 double newValue = value.toDouble( &ok );
677 mData[index.row()].value = newValue;
678 emit dataChanged( index, index );
679 emit classesChanged();
685 mData[index.row()].color = value.value<QColor>();
686 emit dataChanged( index, index );
687 emit classesChanged();
693 mData[index.row()].label = value.toString();
694 emit dataChanged( index, index );
695 emit classesChanged();
703Qt::ItemFlags QgsPalettedRendererModel::flags(
const QModelIndex &index )
const
705 if ( !index.isValid() )
706 return QAbstractItemModel::flags( index ) | Qt::ItemIsDropEnabled;
708 Qt::ItemFlags f = QAbstractItemModel::flags( index );
709 switch ( index.column() )
714 f = f | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
717 return f | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
720bool QgsPalettedRendererModel::removeRows(
int row,
int count,
const QModelIndex &parent )
722 if ( row < 0 || row >= mData.count() )
724 if ( parent.isValid() )
727 for (
int i = row + count - 1; i >= row; --i )
729 beginRemoveRows( parent, i, i );
733 emit classesChanged();
737bool QgsPalettedRendererModel::insertRows(
int row,
int count,
const QModelIndex & )
739 QgsPalettedRasterRenderer::ClassData::const_iterator cIt = mData.constBegin();
740 int currentMaxValue = -std::numeric_limits<int>::max();
741 for ( ; cIt != mData.constEnd(); ++cIt )
743 int value = cIt->value;
744 currentMaxValue = std::max( value, currentMaxValue );
746 int nextValue = std::max( 0, currentMaxValue + 1 );
748 beginInsertRows( QModelIndex(), row, row + count - 1 );
749 for (
int i = row; i < row + count; ++i, ++nextValue )
754 emit classesChanged();
758Qt::DropActions QgsPalettedRendererModel::supportedDropActions()
const
760 return Qt::MoveAction;
763QStringList QgsPalettedRendererModel::mimeTypes()
const
766 types << u
"application/x-qgspalettedrenderermodel"_s;
770QMimeData *QgsPalettedRendererModel::mimeData(
const QModelIndexList &indexes )
const
772 QMimeData *mimeData =
new QMimeData();
773 QByteArray encodedData;
775 QDataStream stream( &encodedData, QIODevice::WriteOnly );
778 const auto constIndexes = indexes;
779 for (
const QModelIndex &index : constIndexes )
781 if ( !index.isValid() || index.column() != 0 )
784 stream << index.row();
786 mimeData->setData( u
"application/x-qgspalettedrenderermodel"_s, encodedData );
790bool QgsPalettedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex & )
793 if ( action != Qt::MoveAction )
796 if ( !data->hasFormat( u
"application/x-qgspalettedrenderermodel"_s ) )
799 QByteArray encodedData = data->data( u
"application/x-qgspalettedrenderermodel"_s );
800 QDataStream stream( &encodedData, QIODevice::ReadOnly );
803 while ( !stream.atEnd() )
811 for (
int i = 0; i < rows.count(); ++i )
812 newData << mData.at( rows.at( i ) );
817 beginInsertRows( QModelIndex(), row, row + rows.count() - 1 );
818 for (
int i = 0; i < rows.count(); ++i )
819 mData.insert( row + i, newData.at( i ) );
821 emit classesChanged();
825QModelIndex QgsPalettedRendererModel::addEntry(
const QColor &color )
827 insertRow( rowCount() );
828 QModelIndex newRow = index( mData.count() - 1, 1 );
829 setData( newRow, color );
833void QgsPalettedRendererModel::deleteAll()
838 emit classesChanged();
846 : mProvider( ( layer && layer->dataProvider() ) ? layer->dataProvider()->clone() : nullptr )
847 , mBandNumber( bandNumber )
849 , mClasses( existingClasses )
853void QgsPalettedRendererClassGatherer::run()
855 mWasCanceled =
false;
866 QgsPalettedRasterRenderer::ClassData::iterator classIt = newClasses.begin();
867 emit progressChanged( 0 );
869 for ( ; classIt != newClasses.end(); ++classIt )
874 if ( existingClass.value == classIt->value )
876 classIt->color = existingClass.color;
877 classIt->label = existingClass.label;
882 emit progressChanged( 100 * (
static_cast<double>( i ) /
static_cast<double>( newClasses.count() ) ) );
884 mClasses = newClasses;
888 mFeedbackMutex.lock();
891 mFeedbackMutex.unlock();
893 emit collectedClasses();
900 for (
int i = 0; i < rowCount(); ++i )
902 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.