23 #include "qgssettings.h"
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 ) );
81 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ValueColumn, mValueDelegate );
83 mTreeView->setColumnWidth( QgsPalettedRendererModel::ColorColumn,
Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance(
'X' ) * 6.6 );
84 mTreeView->setContextMenuPolicy( Qt::CustomContextMenu );
85 mTreeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
86 mTreeView->setDragEnabled(
true );
87 mTreeView->setAcceptDrops(
true );
88 mTreeView->setDropIndicatorShown(
true );
89 mTreeView->setDragDropMode( QAbstractItemView::InternalMove );
90 mTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
91 mTreeView->setDefaultDropAction( Qt::MoveAction );
93 connect( mTreeView, &QTreeView::customContextMenuRequested,
this, [ = ]( QPoint ) { mContextMenu->exec( QCursor::pos() ); } );
95 btnColorRamp->setShowRandomColorRamp(
true );
113 connect( mDeleteEntryButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::deleteEntry );
114 connect( mButtonDeleteAll, &QPushButton::clicked, mModel, &QgsPalettedRendererModel::deleteAll );
115 connect( mAddEntryButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::addEntry );
116 connect( mClassifyButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::classify );
124 mLoadFromLayerAction->setEnabled(
false );
143 int bandNumber = mBandComboBox->currentBand();
146 if ( !btnColorRamp->isNull() )
162 mModel->setClassData( pr->
classes() );
187 void QgsPalettedRendererWidget::setSelectionColor(
const QItemSelection &selection,
const QColor &color )
192 QModelIndex colorIndex;
193 const auto constSelection = selection;
194 for (
const QItemSelectionRange &range : constSelection )
196 const auto constIndexes = range.indexes();
197 for (
const QModelIndex &index : constIndexes )
199 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
200 mModel->setData( colorIndex, color, Qt::EditRole );
208 void QgsPalettedRendererWidget::deleteEntry()
213 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
214 const auto constSel = sel;
215 for (
const QItemSelectionRange &range : constSel )
217 if ( range.isValid() )
218 mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
226 void QgsPalettedRendererWidget::addEntry()
230 QColor color( 150, 150, 150 );
231 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
234 color = ramp->color( 1.0 );
236 QModelIndex newEntry = mModel->addEntry( color );
237 mTreeView->scrollTo( newEntry );
238 mTreeView->selectionModel()->select( mProxyModel->mapFromSource( newEntry ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
243 void QgsPalettedRendererWidget::changeColor()
245 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
246 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
247 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
250 if ( panel && panel->dockMode() )
256 panel->openPanel( colorWidget );
262 if ( newColor.isValid() )
264 setSelectionColor( sel, newColor );
269 void QgsPalettedRendererWidget::changeOpacity()
271 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
272 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
273 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
276 double oldOpacity = ( currentColor.alpha() / 255.0 ) * 100.0;
277 double opacity = QInputDialog::getDouble(
this, tr(
"Opacity" ), tr(
"Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
280 int newOpacity = opacity / 100 * 255;
285 const auto constSel = sel;
286 for (
const QItemSelectionRange &range : constSel )
288 const auto constIndexes = range.indexes();
289 for (
const QModelIndex &index : constIndexes )
291 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
293 QColor newColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
294 newColor.setAlpha( newOpacity );
295 mModel->setData( colorIndex, newColor, Qt::EditRole );
304 void QgsPalettedRendererWidget::changeLabel()
306 QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
307 QModelIndex labelIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::LabelColumn );
308 QString currentLabel = mModel->data( labelIndex, Qt::DisplayRole ).toString();
311 QString newLabel = QInputDialog::getText(
this, tr(
"Label" ), tr(
"Change label" ), QLineEdit::Normal, currentLabel, &ok );
317 const auto constSel = sel;
318 for (
const QItemSelectionRange &range : constSel )
320 const auto constIndexes = range.indexes();
321 for (
const QModelIndex &index : constIndexes )
323 labelIndex = mModel->index( index.row(), QgsPalettedRendererModel::LabelColumn );
324 mModel->setData( labelIndex, newLabel, Qt::EditRole );
333 void QgsPalettedRendererWidget::applyColorRamp()
335 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
344 QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
346 double numberOfEntries = data.count();
353 randomRamp->setTotalColorCount( numberOfEntries );
356 if ( numberOfEntries > 1 )
357 numberOfEntries -= 1;
359 for ( ; cIt != data.end(); ++cIt )
361 cIt->color = ramp->color( i / numberOfEntries );
364 mModel->setClassData( data );
370 void QgsPalettedRendererWidget::loadColorTable()
372 QgsSettings settings;
373 QString lastDir = settings.value( QStringLiteral(
"lastColorMapDir" ), QDir::homePath() ).toString();
374 QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Load Color Table from File" ), lastDir );
375 if ( !fileName.isEmpty() )
378 if ( !classes.isEmpty() )
381 mModel->setClassData( classes );
387 QMessageBox::critical(
nullptr, tr(
"Load Color Table" ), tr(
"Could not interpret file as a raster color table." ) );
392 void QgsPalettedRendererWidget::saveColorTable()
394 QgsSettings settings;
395 QString lastDir = settings.value( QStringLiteral(
"lastColorMapDir" ), QDir::homePath() ).toString();
396 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Color Table as File" ), lastDir, tr(
"Text (*.clr)" ) );
397 if ( !fileName.isEmpty() )
399 if ( !fileName.endsWith( QLatin1String(
".clr" ), Qt::CaseInsensitive ) )
401 fileName = fileName +
".clr";
404 QFile outputFile( fileName );
405 if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
407 QTextStream outputStream( &outputFile );
409 outputStream.flush();
412 QFileInfo fileInfo( fileName );
413 settings.setValue( QStringLiteral(
"lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
417 QMessageBox::warning(
this, tr(
"Save Color Table as File" ), tr(
"Write access denied. Adjust the file permissions and try again.\n\n" ) );
422 void QgsPalettedRendererWidget::classify()
438 mGatherer =
new QgsPalettedRendererClassGatherer(
mRasterLayer, mBandComboBox->currentBand(), mModel->classData(), btnColorRamp->colorRamp() );
440 connect( mGatherer, &QgsPalettedRendererClassGatherer::progressChanged, mCalculatingProgressBar, [ = ](
int progress )
442 mCalculatingProgressBar->setValue( progress );
445 mCalculatingProgressBar->show();
446 mCancelButton->show();
447 connect( mCancelButton, &QPushButton::clicked, mGatherer, &QgsPalettedRendererClassGatherer::stop );
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 );
457 void QgsPalettedRendererWidget::loadFromLayer()
463 QList<QgsColorRampShader::ColorRampItem> table = provider->
colorTable( mBandComboBox->currentBand() );
464 if ( !table.isEmpty() )
467 mModel->setClassData( classes );
473 void QgsPalettedRendererWidget::bandChanged(
int band )
483 bool deleteExisting =
false;
484 if ( !mModel->classData().isEmpty() )
486 int res = QMessageBox::question(
this,
487 tr(
"Delete Classification" ),
488 tr(
"The classification band was changed from %1 to %2.\n"
489 "Should the existing classes be deleted?" ).arg( mBand ).arg( band ),
490 QMessageBox::Yes | QMessageBox::No );
492 deleteExisting = ( res == QMessageBox::Yes );
496 mModel->blockSignals(
true );
497 if ( deleteExisting )
500 mModel->blockSignals(
false );
504 void QgsPalettedRendererWidget::gatheredClasses()
506 if ( !mGatherer || mGatherer->wasCanceled() )
509 mModel->setClassData( mGatherer->classes() );
513 void QgsPalettedRendererWidget::gathererThreadFinished()
515 mGatherer->deleteLater();
517 mClassifyButton->setText( tr(
"Classify" ) );
518 mClassifyButton->setEnabled(
true );
519 mCalculatingProgressBar->hide();
520 mCancelButton->hide();
523 void QgsPalettedRendererWidget::layerWillBeRemoved(
QgsMapLayer *layer )
537 QgsPalettedRendererModel::QgsPalettedRendererModel( QObject *parent )
538 : QAbstractItemModel( parent )
550 QModelIndex QgsPalettedRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const
552 if ( column < 0 || column >= columnCount() )
555 return QModelIndex();
558 if ( !parent.isValid() && row >= 0 && row < mData.size() )
561 return createIndex( row, column );
565 return QModelIndex();
568 QModelIndex QgsPalettedRendererModel::parent(
const QModelIndex &index )
const
573 return QModelIndex();
576 int QgsPalettedRendererModel::columnCount(
const QModelIndex &parent )
const
578 if ( parent.isValid() )
584 int QgsPalettedRendererModel::rowCount(
const QModelIndex &parent )
const
586 if ( parent.isValid() )
589 return mData.count();
592 QVariant QgsPalettedRendererModel::data(
const QModelIndex &index,
int role )
const
594 if ( !index.isValid() )
599 case Qt::DisplayRole:
602 switch ( index.column() )
605 return mData.at( index.row() ).value;
608 return mData.at( index.row() ).color;
611 return mData.at( index.row() ).label;
622 QVariant QgsPalettedRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
624 switch ( orientation )
633 case Qt::DisplayRole:
638 return tr(
"Value" );
641 return tr(
"Color" );
644 return tr(
"Label" );
653 return QAbstractItemModel::headerData( section, orientation, role );
655 return QAbstractItemModel::headerData( section, orientation, role );
658 bool 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();
700 Qt::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;
717 bool 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();
734 bool 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();
755 Qt::DropActions QgsPalettedRendererModel::supportedDropActions()
const
757 return Qt::MoveAction;
760 QStringList QgsPalettedRendererModel::mimeTypes()
const
763 types << QStringLiteral(
"application/x-qgspalettedrenderermodel" );
767 QMimeData *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 );
787 bool QgsPalettedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex & )
790 if ( action != Qt::MoveAction )
return true;
792 if ( !data->hasFormat( QStringLiteral(
"application/x-qgspalettedrenderermodel" ) ) )
795 QByteArray encodedData = data->data( QStringLiteral(
"application/x-qgspalettedrenderermodel" ) );
796 QDataStream stream( &encodedData, QIODevice::ReadOnly );
799 while ( !stream.atEnd() )
807 for (
int i = 0; i < rows.count(); ++i )
808 newData << mData.at( rows.at( i ) );
813 beginInsertRows( QModelIndex(), row, row + rows.count() - 1 );
814 for (
int i = 0; i < rows.count(); ++i )
815 mData.insert( row + i, newData.at( i ) );
817 emit classesChanged();
821 QModelIndex QgsPalettedRendererModel::addEntry(
const QColor &color )
823 insertRow( rowCount() );
824 QModelIndex newRow = index( mData.count() - 1, 1 );
825 setData( newRow, color );
829 void QgsPalettedRendererModel::deleteAll()
834 emit classesChanged();
837 void QgsPalettedRendererClassGatherer::run()
839 mWasCanceled =
false;
848 QgsPalettedRasterRenderer::ClassData::iterator classIt = newClasses.begin();
849 emit progressChanged( 0 );
851 for ( ; classIt != newClasses.end(); ++classIt )
856 if ( existingClass.value == classIt->value )
858 classIt->color = existingClass.color;
859 classIt->label = existingClass.label;
864 emit progressChanged( 100 * ( i /
static_cast<float>( newClasses.count() ) ) );
866 mClasses = newClasses;
869 mFeedbackMutex.lock();
872 mFeedbackMutex.unlock();
874 emit collectedClasses();
881 for (
int i = 0; i < rowCount( ); ++i )
883 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.
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.
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.