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 );
79#ifdef ENABLE_MODELTEST
80 new ModelTest( mModel,
this );
83 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ColorColumn,
new QgsColorSwatchDelegate(
this ) );
85 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ValueColumn, mValueDelegate );
87 mTreeView->setColumnWidth( QgsPalettedRendererModel::ColorColumn,
Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance(
'X' ) * 6.6 );
88 mTreeView->setContextMenuPolicy( Qt::CustomContextMenu );
89 mTreeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
90 mTreeView->setDragEnabled(
true );
91 mTreeView->setAcceptDrops(
true );
92 mTreeView->setDropIndicatorShown(
true );
93 mTreeView->setDragDropMode( QAbstractItemView::InternalMove );
94 mTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
95 mTreeView->setDefaultDropAction( Qt::MoveAction );
97 connect( mTreeView, &QTreeView::customContextMenuRequested,
this, [
this]( QPoint ) { mContextMenu->exec( QCursor::pos() ); } );
99 btnColorRamp->setShowRandomColorRamp(
true );
117 connect( mDeleteEntryButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::deleteEntry );
118 connect( mButtonDeleteAll, &QPushButton::clicked, mModel, &QgsPalettedRendererModel::deleteAll );
119 connect( mAddEntryButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::addEntry );
120 connect( mClassifyButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::classify );
124 mLoadFromLayerAction->setEnabled( !
mRasterLayer->dataProvider()->colorTable( mBandComboBox->currentBand() ).isEmpty() );
128 mLoadFromLayerAction->setEnabled(
false );
147 int bandNumber = mBandComboBox->currentBand();
150 if ( !btnColorRamp->isNull() )
166 mModel->setClassData( pr->
classes() );
187 mValueDelegate->setDataType(
mRasterLayer->dataProvider()->dataType( mBand ) );
191void QgsPalettedRendererWidget::setSelectionColor(
const QItemSelection &selection,
const QColor &color )
196 QModelIndex colorIndex;
197 const auto constSelection = selection;
198 for (
const QItemSelectionRange &range : constSelection )
200 const auto constIndexes = range.indexes();
201 for (
const QModelIndex &index : constIndexes )
203 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
204 mModel->setData( colorIndex, color, Qt::EditRole );
212void QgsPalettedRendererWidget::deleteEntry()
217 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
218 const auto constSel = sel;
219 for (
const QItemSelectionRange &range : constSel )
221 if ( range.isValid() )
222 mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
230void QgsPalettedRendererWidget::addEntry()
234 QColor color( 150, 150, 150 );
235 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
238 color = ramp->color( 1.0 );
240 QModelIndex newEntry = mModel->addEntry( color );
241 mTreeView->scrollTo( newEntry );
242 mTreeView->selectionModel()->select( mProxyModel->mapFromSource( newEntry ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
247void QgsPalettedRendererWidget::changeColor()
249 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
250 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
251 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
254 if ( panel && panel->dockMode() )
260 panel->openPanel( colorWidget );
266 if ( newColor.isValid() )
268 setSelectionColor( sel, newColor );
273void QgsPalettedRendererWidget::changeOpacity()
275 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
276 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
277 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
280 double oldOpacity = ( currentColor.alpha() / 255.0 ) * 100.0;
281 double opacity = QInputDialog::getDouble(
this, tr(
"Opacity" ), tr(
"Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
284 int newOpacity = opacity / 100 * 255;
289 const auto constSel = sel;
290 for (
const QItemSelectionRange &range : constSel )
292 const auto constIndexes = range.indexes();
293 for (
const QModelIndex &index : constIndexes )
295 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
297 QColor newColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
298 newColor.setAlpha( newOpacity );
299 mModel->setData( colorIndex, newColor, Qt::EditRole );
308void QgsPalettedRendererWidget::changeLabel()
310 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
311 QModelIndex labelIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::LabelColumn );
312 QString currentLabel = mModel->data( labelIndex, Qt::DisplayRole ).toString();
315 QString newLabel = QInputDialog::getText(
this, tr(
"Label" ), tr(
"Change label" ), QLineEdit::Normal, currentLabel, &ok );
321 const auto constSel = sel;
322 for (
const QItemSelectionRange &range : constSel )
324 const auto constIndexes = range.indexes();
325 for (
const QModelIndex &index : constIndexes )
327 labelIndex = mModel->index( index.row(), QgsPalettedRendererModel::LabelColumn );
328 mModel->setData( labelIndex, newLabel, Qt::EditRole );
337void QgsPalettedRendererWidget::applyColorRamp()
339 std::unique_ptr<QgsColorRamp> ramp( btnColorRamp->colorRamp() );
348 QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
350 double numberOfEntries = data.count();
353 if ( QgsRandomColorRamp *randomRamp =
dynamic_cast<QgsRandomColorRamp *
>( ramp.get() ) )
357 randomRamp->setTotalColorCount( numberOfEntries );
360 if ( numberOfEntries > 1 )
361 numberOfEntries -= 1;
363 for ( ; cIt != data.end(); ++cIt )
365 cIt->color = ramp->color( i / numberOfEntries );
368 mModel->setClassData( data );
374void QgsPalettedRendererWidget::loadColorTable()
376 QgsSettings settings;
377 QString lastDir = settings.
value( u
"lastColorMapDir"_s, QDir::homePath() ).toString();
378 QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Load Color Table from File" ), lastDir );
379 if ( !fileName.isEmpty() )
382 if ( !classes.isEmpty() )
385 mModel->setClassData( classes );
391 QMessageBox::critical(
nullptr, tr(
"Load Color Table" ), tr(
"Could not interpret file as a raster color table." ) );
396void QgsPalettedRendererWidget::saveColorTable()
398 QgsSettings settings;
399 QString lastDir = settings.
value( u
"lastColorMapDir"_s, QDir::homePath() ).toString();
400 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Color Table as File" ), lastDir, tr(
"Text (*.clr)" ) );
401 if ( !fileName.isEmpty() )
403 if ( !fileName.endsWith(
".clr"_L1, Qt::CaseInsensitive ) )
405 fileName = fileName +
".clr";
408 QFile outputFile( fileName );
409 if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
411 QTextStream outputStream( &outputFile );
413 outputStream.flush();
416 QFileInfo fileInfo( fileName );
417 settings.
setValue( u
"lastColorMapDir"_s, fileInfo.absoluteDir().absolutePath() );
421 QMessageBox::warning(
this, tr(
"Save Color Table as File" ), tr(
"Write access denied. Adjust the file permissions and try again.\n\n" ) );
426void QgsPalettedRendererWidget::classify()
430 QgsRasterDataProvider *provider =
mRasterLayer->dataProvider();
442 mGatherer =
new QgsPalettedRendererClassGatherer(
mRasterLayer, mBandComboBox->currentBand(), mModel->classData(), btnColorRamp->colorRamp() );
444 connect( mGatherer, &QgsPalettedRendererClassGatherer::progressChanged, mCalculatingProgressBar, [
this](
int progress ) { mCalculatingProgressBar->setValue( progress ); } );
446 mCalculatingProgressBar->show();
447 mCancelButton->show();
448 connect( mCancelButton, &QPushButton::clicked, mGatherer, &QgsPalettedRendererClassGatherer::stop );
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 );
458void QgsPalettedRendererWidget::loadFromLayer()
461 QgsRasterDataProvider *provider =
mRasterLayer->dataProvider();
464 QList<QgsColorRampShader::ColorRampItem> table = provider->
colorTable( mBandComboBox->currentBand() );
465 if ( !table.isEmpty() )
468 mModel->setClassData( classes );
474void QgsPalettedRendererWidget::bandChanged(
int band )
481 mValueDelegate->setDataType(
mRasterLayer->dataProvider()->dataType( mBand ) );
484 bool deleteExisting =
false;
485 if ( !mModel->classData().isEmpty() )
487 int res = QMessageBox::question(
489 tr(
"Delete Classification" ),
491 "The classification band was changed from %1 to %2.\n"
492 "Should the existing classes be deleted?"
496 QMessageBox::Yes | QMessageBox::No
499 deleteExisting = ( res == QMessageBox::Yes );
503 mModel->blockSignals(
true );
504 if ( deleteExisting )
507 mModel->blockSignals(
false );
511void QgsPalettedRendererWidget::gatheredClasses()
513 if ( !mGatherer || mGatherer->wasCanceled() )
516 mModel->setClassData( mGatherer->classes() );
520void QgsPalettedRendererWidget::gathererThreadFinished()
522 mGatherer->deleteLater();
524 mClassifyButton->setText( tr(
"Classify" ) );
525 mClassifyButton->setEnabled(
true );
526 mCalculatingProgressBar->hide();
527 mCancelButton->hide();
530void QgsPalettedRendererWidget::layerWillBeRemoved(
QgsMapLayer *layer )
544QgsPalettedRendererModel::QgsPalettedRendererModel( QObject *parent )
545 : QAbstractItemModel( parent )
555QModelIndex QgsPalettedRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const
557 if ( column < 0 || column >= columnCount() )
560 return QModelIndex();
563 if ( !parent.isValid() && row >= 0 && row < mData.size() )
566 return createIndex( row, column );
570 return QModelIndex();
573QModelIndex QgsPalettedRendererModel::parent(
const QModelIndex &index )
const
578 return QModelIndex();
581int QgsPalettedRendererModel::columnCount(
const QModelIndex &parent )
const
583 if ( parent.isValid() )
589int QgsPalettedRendererModel::rowCount(
const QModelIndex &parent )
const
591 if ( parent.isValid() )
594 return mData.count();
597QVariant QgsPalettedRendererModel::data(
const QModelIndex &index,
int role )
const
599 if ( !index.isValid() )
604 case Qt::DisplayRole:
607 switch ( index.column() )
610 return mData.at( index.row() ).value;
613 return mData.at( index.row() ).color;
616 return mData.at( index.row() ).label;
627QVariant QgsPalettedRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
629 switch ( orientation )
638 case Qt::DisplayRole:
643 return tr(
"Value" );
646 return tr(
"Color" );
649 return tr(
"Label" );
657 return QAbstractItemModel::headerData( section, orientation, role );
659 return QAbstractItemModel::headerData( section, orientation, role );
662bool QgsPalettedRendererModel::setData(
const QModelIndex &index,
const QVariant &value,
int )
664 if ( !index.isValid() )
666 if ( index.row() >= mData.length() )
669 switch ( index.column() )
674 double newValue = value.toDouble( &ok );
678 mData[index.row()].value = newValue;
679 emit dataChanged( index, index );
680 emit classesChanged();
686 mData[index.row()].color = value.value<QColor>();
687 emit dataChanged( index, index );
688 emit classesChanged();
694 mData[index.row()].label = value.toString();
695 emit dataChanged( index, index );
696 emit classesChanged();
704Qt::ItemFlags QgsPalettedRendererModel::flags(
const QModelIndex &index )
const
706 if ( !index.isValid() )
707 return QAbstractItemModel::flags( index ) | Qt::ItemIsDropEnabled;
709 Qt::ItemFlags f = QAbstractItemModel::flags( index );
710 switch ( index.column() )
715 f = f | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
718 return f | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
721bool QgsPalettedRendererModel::removeRows(
int row,
int count,
const QModelIndex &parent )
723 if ( row < 0 || row >= mData.count() )
725 if ( parent.isValid() )
728 for (
int i = row + count - 1; i >= row; --i )
730 beginRemoveRows( parent, i, i );
734 emit classesChanged();
738bool QgsPalettedRendererModel::insertRows(
int row,
int count,
const QModelIndex & )
740 QgsPalettedRasterRenderer::ClassData::const_iterator cIt = mData.constBegin();
741 int currentMaxValue = -std::numeric_limits<int>::max();
742 for ( ; cIt != mData.constEnd(); ++cIt )
744 int value = cIt->value;
745 currentMaxValue = std::max( value, currentMaxValue );
747 int nextValue = std::max( 0, currentMaxValue + 1 );
749 beginInsertRows( QModelIndex(), row, row + count - 1 );
750 for (
int i = row; i < row + count; ++i, ++nextValue )
755 emit classesChanged();
759Qt::DropActions QgsPalettedRendererModel::supportedDropActions()
const
761 return Qt::MoveAction;
764QStringList QgsPalettedRendererModel::mimeTypes()
const
767 types << u
"application/x-qgspalettedrenderermodel"_s;
771QMimeData *QgsPalettedRendererModel::mimeData(
const QModelIndexList &indexes )
const
773 QMimeData *mimeData =
new QMimeData();
774 QByteArray encodedData;
776 QDataStream stream( &encodedData, QIODevice::WriteOnly );
779 const auto constIndexes = indexes;
780 for (
const QModelIndex &index : constIndexes )
782 if ( !index.isValid() || index.column() != 0 )
785 stream << index.row();
787 mimeData->setData( u
"application/x-qgspalettedrenderermodel"_s, encodedData );
791bool QgsPalettedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex & )
794 if ( action != Qt::MoveAction )
797 if ( !data->hasFormat( u
"application/x-qgspalettedrenderermodel"_s ) )
800 QByteArray encodedData = data->data( u
"application/x-qgspalettedrenderermodel"_s );
801 QDataStream stream( &encodedData, QIODevice::ReadOnly );
804 while ( !stream.atEnd() )
812 for (
int i = 0; i < rows.count(); ++i )
813 newData << mData.at( rows.at( i ) );
818 beginInsertRows( QModelIndex(), row, row + rows.count() - 1 );
819 for (
int i = 0; i < rows.count(); ++i )
820 mData.insert( row + i, newData.at( i ) );
822 emit classesChanged();
826QModelIndex QgsPalettedRendererModel::addEntry(
const QColor &color )
828 insertRow( rowCount() );
829 QModelIndex newRow = index( mData.count() - 1, 1 );
830 setData( newRow, color );
834void QgsPalettedRendererModel::deleteAll()
839 emit classesChanged();
847 : mProvider( ( layer && layer->dataProvider() ) ? layer->dataProvider()->clone() : nullptr )
848 , mBandNumber( bandNumber )
850 , mClasses( existingClasses )
854void QgsPalettedRendererClassGatherer::run()
856 mWasCanceled =
false;
867 QgsPalettedRasterRenderer::ClassData::iterator classIt = newClasses.begin();
868 emit progressChanged( 0 );
870 for ( ; classIt != newClasses.end(); ++classIt )
875 if ( existingClass.value == classIt->value )
877 classIt->color = existingClass.color;
878 classIt->label = existingClass.label;
883 emit progressChanged( 100 * (
static_cast<double>( i ) /
static_cast<double>( newClasses.count() ) ) );
885 mClasses = newClasses;
889 mFeedbackMutex.lock();
892 mFeedbackMutex.unlock();
894 emit collectedClasses();
901 for (
int i = 0; i < rowCount(); ++i )
903 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.