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, [ = ](
int progress )
433 mCalculatingProgressBar->setValue( progress );
436 mCalculatingProgressBar->show();
437 mCancelButton->show();
438 connect( mCancelButton, &QPushButton::clicked, mGatherer, &QgsPalettedRendererClassGatherer::stop );
440 connect( mGatherer, &QgsPalettedRendererClassGatherer::collectedClasses,
this, &QgsPalettedRendererWidget::gatheredClasses );
441 connect( mGatherer, &QgsPalettedRendererClassGatherer::finished,
this, &QgsPalettedRendererWidget::gathererThreadFinished );
442 mClassifyButton->setText( tr(
"Calculating…" ) );
443 mClassifyButton->setEnabled(
false );
448 void QgsPalettedRendererWidget::loadFromLayer()
454 QList<QgsColorRampShader::ColorRampItem> table = provider->
colorTable( mBandComboBox->currentBand() );
455 if ( !table.isEmpty() )
458 mModel->setClassData( classes );
464 void QgsPalettedRendererWidget::bandChanged(
int band )
469 bool deleteExisting =
false;
470 if ( !mModel->classData().isEmpty() )
472 int res = QMessageBox::question(
this,
473 tr(
"Delete Classification" ),
474 tr(
"The classification band was changed from %1 to %2.\n"
475 "Should the existing classes be deleted?" ).arg( mBand ).arg( band ),
476 QMessageBox::Yes | QMessageBox::No );
478 deleteExisting = ( res == QMessageBox::Yes );
482 mModel->blockSignals(
true );
483 if ( deleteExisting )
486 mModel->blockSignals(
false );
490 void QgsPalettedRendererWidget::gatheredClasses()
492 if ( !mGatherer || mGatherer->wasCanceled() )
495 mModel->setClassData( mGatherer->classes() );
499 void QgsPalettedRendererWidget::gathererThreadFinished()
501 mGatherer->deleteLater();
503 mClassifyButton->setText( tr(
"Classify" ) );
504 mClassifyButton->setEnabled(
true );
505 mCalculatingProgressBar->hide();
506 mCancelButton->hide();
509 void QgsPalettedRendererWidget::layerWillBeRemoved(
QgsMapLayer *layer )
523 QgsPalettedRendererModel::QgsPalettedRendererModel( QObject *parent )
524 : QAbstractItemModel( parent )
536 QModelIndex QgsPalettedRendererModel::index(
int row,
int column,
const QModelIndex &parent )
const
538 if ( column < 0 || column >= columnCount() )
541 return QModelIndex();
544 if ( !parent.isValid() && row >= 0 && row < mData.size() )
547 return createIndex( row, column );
551 return QModelIndex();
554 QModelIndex QgsPalettedRendererModel::parent(
const QModelIndex &index )
const
559 return QModelIndex();
562 int QgsPalettedRendererModel::columnCount(
const QModelIndex &parent )
const
564 if ( parent.isValid() )
570 int QgsPalettedRendererModel::rowCount(
const QModelIndex &parent )
const
572 if ( parent.isValid() )
575 return mData.count();
578 QVariant QgsPalettedRendererModel::data(
const QModelIndex &index,
int role )
const
580 if ( !index.isValid() )
585 case Qt::DisplayRole:
588 switch ( index.column() )
591 return mData.at( index.row() ).value;
594 return mData.at( index.row() ).color;
597 return mData.at( index.row() ).label;
608 QVariant QgsPalettedRendererModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
610 switch ( orientation )
619 case Qt::DisplayRole:
624 return tr(
"Value" );
627 return tr(
"Color" );
630 return tr(
"Label" );
639 return QAbstractItemModel::headerData( section, orientation, role );
641 return QAbstractItemModel::headerData( section, orientation, role );
644 bool QgsPalettedRendererModel::setData(
const QModelIndex &index,
const QVariant &value,
int )
646 if ( !index.isValid() )
648 if ( index.row() >= mData.length() )
651 switch ( index.column() )
656 int newValue = value.toInt( &ok );
660 mData[ index.row() ].value = newValue;
661 emit dataChanged( index, index );
662 emit classesChanged();
668 mData[ index.row() ].color = value.value<QColor>();
669 emit dataChanged( index, index );
670 emit classesChanged();
676 mData[ index.row() ].label = value.toString();
677 emit dataChanged( index, index );
678 emit classesChanged();
686 Qt::ItemFlags QgsPalettedRendererModel::flags(
const QModelIndex &index )
const
688 if ( !index.isValid() )
689 return QAbstractItemModel::flags( index ) | Qt::ItemIsDropEnabled;
691 Qt::ItemFlags f = QAbstractItemModel::flags( index );
692 switch ( index.column() )
697 f = f | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
700 return f | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
703 bool QgsPalettedRendererModel::removeRows(
int row,
int count,
const QModelIndex &parent )
705 if ( row < 0 || row >= mData.count() )
707 if ( parent.isValid() )
710 for (
int i = row + count - 1; i >= row; --i )
712 beginRemoveRows( parent, i, i );
716 emit classesChanged();
720 bool QgsPalettedRendererModel::insertRows(
int row,
int count,
const QModelIndex & )
722 QgsPalettedRasterRenderer::ClassData::const_iterator cIt = mData.constBegin();
723 int currentMaxValue = -std::numeric_limits<int>::max();
724 for ( ; cIt != mData.constEnd(); ++cIt )
726 int value = cIt->value;
727 currentMaxValue = std::max( value, currentMaxValue );
729 int nextValue = std::max( 0, currentMaxValue + 1 );
731 beginInsertRows( QModelIndex(), row, row + count - 1 );
732 for (
int i = row; i < row + count; ++i, ++nextValue )
737 emit classesChanged();
741 Qt::DropActions QgsPalettedRendererModel::supportedDropActions()
const
743 return Qt::MoveAction;
746 QStringList QgsPalettedRendererModel::mimeTypes()
const
749 types << QStringLiteral(
"application/x-qgspalettedrenderermodel" );
753 QMimeData *QgsPalettedRendererModel::mimeData(
const QModelIndexList &indexes )
const
755 QMimeData *mimeData =
new QMimeData();
756 QByteArray encodedData;
758 QDataStream stream( &encodedData, QIODevice::WriteOnly );
761 const auto constIndexes = indexes;
762 for (
const QModelIndex &index : constIndexes )
764 if ( !index.isValid() || index.column() != 0 )
767 stream << index.row();
769 mimeData->setData( QStringLiteral(
"application/x-qgspalettedrenderermodel" ), encodedData );
773 bool QgsPalettedRendererModel::dropMimeData(
const QMimeData *data, Qt::DropAction action,
int row,
int column,
const QModelIndex & )
776 if ( action != Qt::MoveAction )
return true;
778 if ( !data->hasFormat( QStringLiteral(
"application/x-qgspalettedrenderermodel" ) ) )
781 QByteArray encodedData = data->data( QStringLiteral(
"application/x-qgspalettedrenderermodel" ) );
782 QDataStream stream( &encodedData, QIODevice::ReadOnly );
785 while ( !stream.atEnd() )
793 for (
int i = 0; i < rows.count(); ++i )
794 newData << mData.at( rows.at( i ) );
799 beginInsertRows( QModelIndex(), row, row + rows.count() - 1 );
800 for (
int i = 0; i < rows.count(); ++i )
801 mData.insert( row + i, newData.at( i ) );
803 emit classesChanged();
807 QModelIndex QgsPalettedRendererModel::addEntry(
const QColor &color )
809 insertRow( rowCount() );
810 QModelIndex newRow = index( mData.count() - 1, 1 );
811 setData( newRow, color );
815 void QgsPalettedRendererModel::deleteAll()
820 emit classesChanged();
823 void QgsPalettedRendererClassGatherer::run()
825 mWasCanceled =
false;
834 QgsPalettedRasterRenderer::ClassData::iterator classIt = newClasses.begin();
835 emit progressChanged( 0 );
837 for ( ; classIt != newClasses.end(); ++classIt )
842 if ( existingClass.value == classIt->value )
844 classIt->color = existingClass.color;
845 classIt->label = existingClass.label;
850 emit progressChanged( 100 * ( i /
static_cast<float>( newClasses.count() ) ) );
852 mClasses = newClasses;
855 mFeedbackMutex.lock();
858 mFeedbackMutex.unlock();
860 emit collectedClasses();