28 #include <QColorDialog>
29 #include <QInputDialog>
30 #include <QFileDialog>
31 #include <QMessageBox>
34 #include <QTextStream>
36 #ifdef ENABLE_MODELTEST
37 #include "modeltest.h"
45 mCalculatingProgressBar->hide();
46 mCancelButton->hide();
48 mContextMenu =
new QMenu( tr(
"Options" ),
this );
49 mContextMenu->addAction( tr(
"Change Color…" ),
this, SLOT( changeColor() ) );
50 mContextMenu->addAction( tr(
"Change Opacity…" ),
this, SLOT( changeOpacity() ) );
51 mContextMenu->addAction( tr(
"Change Label…" ),
this, SLOT( changeLabel() ) );
53 mAdvancedMenu =
new QMenu( tr(
"Advanced Options" ),
this );
54 QAction *mLoadFromLayerAction = mAdvancedMenu->addAction( tr(
"Load Classes from Layer" ) );
55 connect( mLoadFromLayerAction, &QAction::triggered,
this, &QgsPalettedRendererWidget::loadFromLayer );
56 QAction *loadFromFile = mAdvancedMenu->addAction( tr(
"Load Color Map from File…" ) );
57 connect( loadFromFile, &QAction::triggered,
this, &QgsPalettedRendererWidget::loadColorTable );
58 QAction *exportToFile = mAdvancedMenu->addAction( tr(
"Export Color Map to File…" ) );
59 connect( exportToFile, &QAction::triggered,
this, &QgsPalettedRendererWidget::saveColorTable );
62 mButtonAdvanced->setMenu( mAdvancedMenu );
64 mModel =
new QgsPalettedRendererModel(
this );
65 mProxyModel =
new QgsPalettedRendererProxyModel(
this );
66 mProxyModel->setSourceModel( mModel );
67 mTreeView->setSortingEnabled(
false );
68 mTreeView->setModel( mProxyModel );
72 mProxyModel->sort( QgsPalettedRendererModel::Column::ValueColumn );
75 #ifdef ENABLE_MODELTEST
76 new ModelTest( mModel,
this );
79 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ColorColumn,
new QgsColorSwatchDelegate(
this ) );
80 mValueDelegate =
new QgsLocaleAwareNumericLineEditDelegate( Qgis::DataType::UnknownDataType,
this );
81 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ValueColumn, mValueDelegate );
83 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
84 mTreeView->setColumnWidth( QgsPalettedRendererModel::ColorColumn,
Qgis::UI_SCALE_FACTOR * fontMetrics().width(
'X' ) * 6.6 );
86 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, [ = ]( 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 );
128 mLoadFromLayerAction->setEnabled(
false );
147 int bandNumber = mBandComboBox->currentBand();
150 if ( !btnColorRamp->isNull() )
166 mModel->setClassData( pr->
classes() );
191 void 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 );
212 void 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() );
230 void 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 );
247 void 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 );
273 void 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 );
308 void 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 );
337 void QgsPalettedRendererWidget::applyColorRamp()
339 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
348 QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
350 double numberOfEntries = data.count();
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 );
374 void QgsPalettedRendererWidget::loadColorTable()
377 QString lastDir = settings.
value( QStringLiteral(
"lastColorMapDir" ), 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." ) );
396 void QgsPalettedRendererWidget::saveColorTable()
399 QString lastDir = settings.
value( QStringLiteral(
"lastColorMapDir" ), 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( QLatin1String(
".clr" ), 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( QStringLiteral(
"lastColorMapDir" ), 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" ) );
426 void QgsPalettedRendererWidget::classify()
442 mGatherer =
new QgsPalettedRendererClassGatherer(
mRasterLayer, mBandComboBox->currentBand(), mModel->classData(), btnColorRamp->colorRamp() );
444 connect( mGatherer, &QgsPalettedRendererClassGatherer::progressChanged, mCalculatingProgressBar, [ = ](
int progress )
446 mCalculatingProgressBar->setValue( progress );
449 mCalculatingProgressBar->show();
450 mCancelButton->show();
451 connect( mCancelButton, &QPushButton::clicked, mGatherer, &QgsPalettedRendererClassGatherer::stop );
453 connect( mGatherer, &QgsPalettedRendererClassGatherer::collectedClasses,
this, &QgsPalettedRendererWidget::gatheredClasses );
454 connect( mGatherer, &QgsPalettedRendererClassGatherer::finished,
this, &QgsPalettedRendererWidget::gathererThreadFinished );
455 mClassifyButton->setText( tr(
"Calculating…" ) );
456 mClassifyButton->setEnabled(
false );
461 void QgsPalettedRendererWidget::loadFromLayer()
467 QList<QgsColorRampShader::ColorRampItem> table = provider->
colorTable( mBandComboBox->currentBand() );
468 if ( !table.isEmpty() )
471 mModel->setClassData( classes );
477 void QgsPalettedRendererWidget::bandChanged(
int band )
487 bool deleteExisting =
false;
488 if ( !mModel->classData().isEmpty() )
490 int res = QMessageBox::question(
this,
491 tr(
"Delete Classification" ),
492 tr(
"The classification band was changed from %1 to %2.\n"
493 "Should the existing classes be deleted?" ).arg( mBand ).arg( band ),
494 QMessageBox::Yes | QMessageBox::No );
496 deleteExisting = ( res == QMessageBox::Yes );
500 mModel->blockSignals(
true );
501 if ( deleteExisting )
504 mModel->blockSignals(
false );
508 void QgsPalettedRendererWidget::gatheredClasses()
510 if ( !mGatherer || mGatherer->wasCanceled() )
513 mModel->setClassData( mGatherer->classes() );
517 void QgsPalettedRendererWidget::gathererThreadFinished()
519 mGatherer->deleteLater();
521 mClassifyButton->setText( tr(
"Classify" ) );
522 mClassifyButton->setEnabled(
true );
523 mCalculatingProgressBar->hide();
524 mCancelButton->hide();
527 void QgsPalettedRendererWidget::layerWillBeRemoved(
QgsMapLayer *layer )
541 QgsPalettedRendererModel::QgsPalettedRendererModel( QObject *parent )
542 : QAbstractItemModel( parent )
554 QModelIndex 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();
572 QModelIndex QgsPalettedRendererModel::parent(
const QModelIndex &index )
const
577 return QModelIndex();
580 int QgsPalettedRendererModel::columnCount(
const QModelIndex &parent )
const
582 if ( parent.isValid() )
588 int QgsPalettedRendererModel::rowCount(
const QModelIndex &parent )
const
590 if ( parent.isValid() )
593 return mData.count();
596 QVariant 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;
626 QVariant 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" );
657 return QAbstractItemModel::headerData( section, orientation, role );
659 return QAbstractItemModel::headerData( section, orientation, role );
662 bool 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();
704 Qt::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;
721 bool 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();
738 bool 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();
759 Qt::DropActions QgsPalettedRendererModel::supportedDropActions()
const
761 return Qt::MoveAction;
764 QStringList QgsPalettedRendererModel::mimeTypes()
const
767 types << QStringLiteral(
"application/x-qgspalettedrenderermodel" );
771 QMimeData *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( QStringLiteral(
"application/x-qgspalettedrenderermodel" ), encodedData );
791 bool QgsPalettedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex & )
794 if ( action != Qt::MoveAction )
return true;
796 if ( !data->hasFormat( QStringLiteral(
"application/x-qgspalettedrenderermodel" ) ) )
799 QByteArray encodedData = data->data( QStringLiteral(
"application/x-qgspalettedrenderermodel" ) );
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();
825 QModelIndex QgsPalettedRendererModel::addEntry(
const QColor &color )
827 insertRow( rowCount() );
828 QModelIndex newRow = index( mData.count() - 1, 1 );
829 setData( newRow, color );
833 void QgsPalettedRendererModel::deleteAll()
838 emit classesChanged();
841 void QgsPalettedRendererClassGatherer::run()
843 mWasCanceled =
false;
852 QgsPalettedRasterRenderer::ClassData::iterator classIt = newClasses.begin();
853 emit progressChanged( 0 );
855 for ( ; classIt != newClasses.end(); ++classIt )
860 if ( existingClass.value == classIt->value )
862 classIt->color = existingClass.color;
863 classIt->label = existingClass.label;
868 emit progressChanged( 100 * ( i /
static_cast<float>( newClasses.count() ) ) );
870 mClasses = newClasses;
873 mFeedbackMutex.lock();
876 mFeedbackMutex.unlock();
878 emit collectedClasses();
885 for (
int i = 0; i < rowCount( ); ++i )
887 data.push_back( qobject_cast<QgsPalettedRendererModel *>( sourceModel() )->classAtIndex( mapToSource( index( i, 0 ) ) ) );
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.
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.
int band() const
Returns the raster band used for rendering the raster.
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.
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.
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.
virtual QList< QgsColorRampShader::ColorRampItem > colorTable(int bandNo) const
Qgis::DataType dataType(int bandNo) const override=0
Returns data type for the band specified by number.
Represents a raster layer.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
QgsRasterRenderer * renderer() const
Returns the raster's renderer.
Raster renderer pipe that applies colors to a raster.
A rectangle specified with double values.
This class is a composition of two QSettings instances:
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.