26 #include <QColorDialog>
27 #include <QInputDialog>
28 #include <QFileDialog>
29 #include <QMessageBox>
32 #ifdef ENABLE_MODELTEST
33 #include "modeltest.h"
40 mCalculatingProgressBar->hide();
41 mCancelButton->hide();
43 mContextMenu =
new QMenu( tr(
"Options" ),
this );
44 mContextMenu->addAction( tr(
"Change Color…" ),
this, SLOT( changeColor() ) );
45 mContextMenu->addAction( tr(
"Change Opacity…" ),
this, SLOT( changeOpacity() ) );
46 mContextMenu->addAction( tr(
"Change Label…" ),
this, SLOT( changeLabel() ) );
48 mAdvancedMenu =
new QMenu( tr(
"Advanced Options" ),
this );
49 QAction *mLoadFromLayerAction = mAdvancedMenu->addAction( tr(
"Load Classes from Layer" ) );
50 connect( mLoadFromLayerAction, &QAction::triggered,
this, &QgsPalettedRendererWidget::loadFromLayer );
51 QAction *loadFromFile = mAdvancedMenu->addAction( tr(
"Load Color Map from File…" ) );
52 connect( loadFromFile, &QAction::triggered,
this, &QgsPalettedRendererWidget::loadColorTable );
53 QAction *exportToFile = mAdvancedMenu->addAction( tr(
"Export Color Map to File…" ) );
54 connect( exportToFile, &QAction::triggered,
this, &QgsPalettedRendererWidget::saveColorTable );
57 mButtonAdvanced->setMenu( mAdvancedMenu );
59 mModel =
new QgsPalettedRendererModel(
this );
60 mTreeView->setSortingEnabled(
false );
61 mTreeView->setModel( mModel );
63 #ifdef ENABLE_MODELTEST
64 new ModelTest( mModel,
this );
68 mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ColorColumn, mSwatchDelegate );
70 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
71 mTreeView->setColumnWidth( QgsPalettedRendererModel::ColorColumn,
Qgis::UI_SCALE_FACTOR * fontMetrics().width(
'X' ) * 6.6 );
73 mTreeView->setColumnWidth( QgsPalettedRendererModel::ColorColumn,
Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance(
'X' ) * 6.6 );
75 mTreeView->setContextMenuPolicy( Qt::CustomContextMenu );
76 mTreeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
77 mTreeView->setDragEnabled(
true );
78 mTreeView->setAcceptDrops(
true );
79 mTreeView->setDropIndicatorShown(
true );
80 mTreeView->setDragDropMode( QAbstractItemView::InternalMove );
81 mTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
82 mTreeView->setDefaultDropAction( Qt::MoveAction );
84 connect( mTreeView, &QTreeView::customContextMenuRequested,
this, [ = ]( QPoint ) { mContextMenu->exec( QCursor::pos() ); }
87 btnColorRamp->setShowRandomColorRamp(
true );
105 connect( mDeleteEntryButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::deleteEntry );
106 connect( mButtonDeleteAll, &QPushButton::clicked, mModel, &QgsPalettedRendererModel::deleteAll );
107 connect( mAddEntryButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::addEntry );
108 connect( mClassifyButton, &QPushButton::clicked,
this, &QgsPalettedRendererWidget::classify );
113 mLoadFromLayerAction->setEnabled( !provider->
colorTable( mBandComboBox->currentBand() ).isEmpty() );
117 mLoadFromLayerAction->setEnabled(
false );
136 int bandNumber = mBandComboBox->currentBand();
139 if ( !btnColorRamp->isNull() )
155 mModel->setClassData( pr->
classes() );
175 void QgsPalettedRendererWidget::setSelectionColor(
const QItemSelection &selection,
const QColor &color )
180 QModelIndex colorIndex;
181 const auto constSelection = selection;
182 for (
const QItemSelectionRange &range : constSelection )
184 const auto constIndexes = range.indexes();
185 for (
const QModelIndex &index : constIndexes )
187 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
188 mModel->setData( colorIndex, color, Qt::EditRole );
196 void QgsPalettedRendererWidget::deleteEntry()
201 QItemSelection sel = mTreeView->selectionModel()->selection();
202 const auto constSel = sel;
203 for (
const QItemSelectionRange &range : constSel )
205 if ( range.isValid() )
206 mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
214 void QgsPalettedRendererWidget::addEntry()
218 QColor color( 150, 150, 150 );
219 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
222 color = ramp->color( 1.0 );
224 QModelIndex newEntry = mModel->addEntry( color );
225 mTreeView->scrollTo( newEntry );
226 mTreeView->selectionModel()->select( newEntry, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
231 void QgsPalettedRendererWidget::changeColor()
233 QItemSelection sel = mTreeView->selectionModel()->selection();
235 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
236 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
239 if ( panel && panel->dockMode() )
245 panel->openPanel( colorWidget );
251 if ( newColor.isValid() )
253 setSelectionColor( sel, newColor );
258 void QgsPalettedRendererWidget::changeOpacity()
260 QItemSelection sel = mTreeView->selectionModel()->selection();
262 QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
263 QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
266 double oldOpacity = ( currentColor.alpha() / 255.0 ) * 100.0;
267 double opacity = QInputDialog::getDouble(
this, tr(
"Opacity" ), tr(
"Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
270 int newOpacity = opacity / 100 * 255;
275 const auto constSel = sel;
276 for (
const QItemSelectionRange &range : constSel )
278 const auto constIndexes = range.indexes();
279 for (
const QModelIndex &index : constIndexes )
281 colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
283 QColor newColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
284 newColor.setAlpha( newOpacity );
285 mModel->setData( colorIndex, newColor, Qt::EditRole );
294 void QgsPalettedRendererWidget::changeLabel()
296 QItemSelection sel = mTreeView->selectionModel()->selection();
298 QModelIndex labelIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::LabelColumn );
299 QString currentLabel = mModel->data( labelIndex, Qt::DisplayRole ).toString();
302 QString newLabel = QInputDialog::getText(
this, tr(
"Label" ), tr(
"Change label" ), QLineEdit::Normal, currentLabel, &ok );
308 const auto constSel = sel;
309 for (
const QItemSelectionRange &range : constSel )
311 const auto constIndexes = range.indexes();
312 for (
const QModelIndex &index : constIndexes )
314 labelIndex = mModel->index( index.row(), QgsPalettedRendererModel::LabelColumn );
315 mModel->setData( labelIndex, newLabel, Qt::EditRole );
324 void QgsPalettedRendererWidget::applyColorRamp()
326 std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
335 QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
337 double numberOfEntries = data.count();
344 randomRamp->setTotalColorCount( numberOfEntries );
347 if ( numberOfEntries > 1 )
348 numberOfEntries -= 1;
350 for ( ; cIt != data.end(); ++cIt )
352 cIt->color = ramp->color( i / numberOfEntries );
355 mModel->setClassData( data );
361 void QgsPalettedRendererWidget::loadColorTable()
364 QString lastDir = settings.
value( QStringLiteral(
"lastColorMapDir" ), QDir::homePath() ).toString();
365 QString fileName = QFileDialog::getOpenFileName(
this, tr(
"Load Color Table from File" ), lastDir );
366 if ( !fileName.isEmpty() )
369 if ( !classes.isEmpty() )
372 mModel->setClassData( classes );
378 QMessageBox::critical(
nullptr, tr(
"Load Color Table" ), tr(
"Could not interpret file as a raster color table." ) );
383 void QgsPalettedRendererWidget::saveColorTable()
386 QString lastDir = settings.
value( QStringLiteral(
"lastColorMapDir" ), QDir::homePath() ).toString();
387 QString fileName = QFileDialog::getSaveFileName(
this, tr(
"Save Color Table as File" ), lastDir, tr(
"Text (*.clr)" ) );
388 if ( !fileName.isEmpty() )
390 if ( !fileName.endsWith( QLatin1String(
".clr" ), Qt::CaseInsensitive ) )
392 fileName = fileName +
".clr";
395 QFile outputFile( fileName );
396 if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
398 QTextStream outputStream( &outputFile );
400 outputStream.flush();
403 QFileInfo fileInfo( fileName );
404 settings.
setValue( QStringLiteral(
"lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
408 QMessageBox::warning(
this, tr(
"Save Color Table as File" ), tr(
"Write access denied. Adjust the file permissions and try again.\n\n" ) );
413 void QgsPalettedRendererWidget::classify()
429 mGatherer =
new QgsPalettedRendererClassGatherer(
mRasterLayer, mBandComboBox->currentBand(), mModel->classData(), btnColorRamp->colorRamp() );
431 connect( mGatherer, &QgsPalettedRendererClassGatherer::progressChanged, mCalculatingProgressBar, &QProgressBar::setValue );
432 mCalculatingProgressBar->show();
433 mCancelButton->show();
434 connect( mCancelButton, &QPushButton::clicked, mGatherer, &QgsPalettedRendererClassGatherer::stop );
436 connect( mGatherer, &QgsPalettedRendererClassGatherer::collectedClasses,
this, &QgsPalettedRendererWidget::gatheredClasses );
437 connect( mGatherer, &QgsPalettedRendererClassGatherer::finished,
this, &QgsPalettedRendererWidget::gathererThreadFinished );
438 mClassifyButton->setText( tr(
"Calculating…" ) );
439 mClassifyButton->setEnabled(
false );
444 void QgsPalettedRendererWidget::loadFromLayer()
450 QList<QgsColorRampShader::ColorRampItem> table = provider->
colorTable( mBandComboBox->currentBand() );
451 if ( !table.isEmpty() )
454 mModel->setClassData( classes );
460 void QgsPalettedRendererWidget::bandChanged(
int band )
465 bool deleteExisting =
false;
466 if ( !mModel->classData().isEmpty() )
468 int res = QMessageBox::question(
this,
469 tr(
"Delete Classification" ),
470 tr(
"The classification band was changed from %1 to %2.\n"
471 "Should the existing classes be deleted?" ).arg( mBand ).arg( band ),
472 QMessageBox::Yes | QMessageBox::No );
474 deleteExisting = ( res == QMessageBox::Yes );
478 mModel->blockSignals(
true );
479 if ( deleteExisting )
482 mModel->blockSignals(
false );
486 void QgsPalettedRendererWidget::gatheredClasses()
488 if ( !mGatherer || mGatherer->wasCanceled() )
491 mModel->setClassData( mGatherer->classes() );
495 void QgsPalettedRendererWidget::gathererThreadFinished()
497 mGatherer->deleteLater();
499 mClassifyButton->setText( tr(
"Classify" ) );
500 mClassifyButton->setEnabled(
true );
501 mCalculatingProgressBar->hide();
502 mCancelButton->hide();
505 void QgsPalettedRendererWidget::layerWillBeRemoved(
QgsMapLayer *layer )
519 QgsPalettedRendererModel::QgsPalettedRendererModel( QObject *parent )
520 : QAbstractItemModel( parent )
532 QModelIndex QgsPalettedRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const
534 if ( column < 0 || column >= columnCount() )
537 return QModelIndex();
540 if ( !parent.isValid() && row >= 0 && row < mData.size() )
543 return createIndex( row, column );
547 return QModelIndex();
550 QModelIndex QgsPalettedRendererModel::parent(
const QModelIndex &index )
const
555 return QModelIndex();
558 int QgsPalettedRendererModel::columnCount(
const QModelIndex &parent )
const
560 if ( parent.isValid() )
566 int QgsPalettedRendererModel::rowCount(
const QModelIndex &parent )
const
568 if ( parent.isValid() )
571 return mData.count();
574 QVariant QgsPalettedRendererModel::data(
const QModelIndex &index,
int role )
const
576 if ( !index.isValid() )
581 case Qt::DisplayRole:
584 switch ( index.column() )
587 return mData.at( index.row() ).value;
590 return mData.at( index.row() ).color;
593 return mData.at( index.row() ).label;
604 QVariant QgsPalettedRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
606 switch ( orientation )
615 case Qt::DisplayRole:
620 return tr(
"Value" );
623 return tr(
"Color" );
626 return tr(
"Label" );
635 return QAbstractItemModel::headerData( section, orientation, role );
637 return QAbstractItemModel::headerData( section, orientation, role );
640 bool QgsPalettedRendererModel::setData(
const QModelIndex &index,
const QVariant &value,
int )
642 if ( !index.isValid() )
644 if ( index.row() >= mData.length() )
647 switch ( index.column() )
652 int newValue = value.toInt( &ok );
656 mData[ index.row() ].value = newValue;
657 emit dataChanged( index, index );
658 emit classesChanged();
664 mData[ index.row() ].color = value.value<QColor>();
665 emit dataChanged( index, index );
666 emit classesChanged();
672 mData[ index.row() ].label = value.toString();
673 emit dataChanged( index, index );
674 emit classesChanged();
682 Qt::ItemFlags QgsPalettedRendererModel::flags(
const QModelIndex &index )
const
684 if ( !index.isValid() )
685 return QAbstractItemModel::flags( index ) | Qt::ItemIsDropEnabled;
687 Qt::ItemFlags f = QAbstractItemModel::flags( index );
688 switch ( index.column() )
693 f = f | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
696 return f | Qt::ItemIsEnabled | Qt::ItemIsSelectable;;
699 bool QgsPalettedRendererModel::removeRows(
int row,
int count,
const QModelIndex &parent )
701 if ( row < 0 || row >= mData.count() )
703 if ( parent.isValid() )
706 for (
int i = row + count - 1; i >= row; --i )
708 beginRemoveRows( parent, i, i );
712 emit classesChanged();
716 bool QgsPalettedRendererModel::insertRows(
int row,
int count,
const QModelIndex & )
718 QgsPalettedRasterRenderer::ClassData::const_iterator cIt = mData.constBegin();
719 int currentMaxValue = -std::numeric_limits<int>::max();
720 for ( ; cIt != mData.constEnd(); ++cIt )
722 int value = cIt->value;
723 currentMaxValue = std::max( value, currentMaxValue );
725 int nextValue = std::max( 0, currentMaxValue + 1 );
727 beginInsertRows( QModelIndex(), row, row + count - 1 );
728 for (
int i = row; i < row + count; ++i, ++nextValue )
733 emit classesChanged();
737 Qt::DropActions QgsPalettedRendererModel::supportedDropActions()
const
739 return Qt::MoveAction;
742 QStringList QgsPalettedRendererModel::mimeTypes()
const
745 types << QStringLiteral(
"application/x-qgspalettedrenderermodel" );
749 QMimeData *QgsPalettedRendererModel::mimeData(
const QModelIndexList &indexes )
const
751 QMimeData *mimeData =
new QMimeData();
752 QByteArray encodedData;
754 QDataStream stream( &encodedData, QIODevice::WriteOnly );
757 const auto constIndexes = indexes;
758 for (
const QModelIndex &index : constIndexes )
760 if ( !index.isValid() || index.column() != 0 )
763 stream << index.row();
765 mimeData->setData( QStringLiteral(
"application/x-qgspalettedrenderermodel" ), encodedData );
769 bool QgsPalettedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex & )
772 if ( action != Qt::MoveAction )
return true;
774 if ( !data->hasFormat( QStringLiteral(
"application/x-qgspalettedrenderermodel" ) ) )
777 QByteArray encodedData = data->data( QStringLiteral(
"application/x-qgspalettedrenderermodel" ) );
778 QDataStream stream( &encodedData, QIODevice::ReadOnly );
781 while ( !stream.atEnd() )
789 for (
int i = 0; i < rows.count(); ++i )
790 newData << mData.at( rows.at( i ) );
795 beginInsertRows( QModelIndex(), row, row + rows.count() - 1 );
796 for (
int i = 0; i < rows.count(); ++i )
797 mData.insert( row + i, newData.at( i ) );
799 emit classesChanged();
803 QModelIndex QgsPalettedRendererModel::addEntry(
const QColor &color )
805 insertRow( rowCount() );
806 QModelIndex newRow = index( mData.count() - 1, 1 );
807 setData( newRow, color );
811 void QgsPalettedRendererModel::deleteAll()
816 emit classesChanged();
819 void QgsPalettedRendererClassGatherer::run()
821 mWasCanceled =
false;
830 QgsPalettedRasterRenderer::ClassData::iterator classIt = newClasses.begin();
831 for ( ; classIt != newClasses.end(); ++classIt )
836 if ( existingClass.value == classIt->value )
838 classIt->color = existingClass.color;
839 classIt->label = existingClass.label;
844 mClasses = newClasses;
847 mFeedbackMutex.lock();
850 mFeedbackMutex.unlock();
852 emit collectedClasses();