1 /***************************************************************************
2                          qgspalettedrendererwidget.cpp
3                          -----------------------------
4     begin                : February 2012
5     copyright            : (C) 2012 by Marco Hugentobler
6     email                : marco at sourcepole dot ch
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgspalettedrendererwidget.h"
19 #include "qgspalettedrasterrenderer.h"
20 #include "qgsrasterdataprovider.h"
21 #include "qgsrasterlayer.h"
22 #include "qgscolordialog.h"
23 #include "qgssettings.h"
24 #include "qgsproject.h"
25 #include "qgscolorrampshaderwidget.h"
26 #include "qgslocaleawarenumericlineeditdelegate.h"
27 
28 #include <QColorDialog>
29 #include <QInputDialog>
30 #include <QFileDialog>
31 #include <QMessageBox>
32 #include <QMenu>
33 #include <QMimeData>
34 #include <QTextStream>
35 
36 #ifdef ENABLE_MODELTEST
37 #include "modeltest.h"
38 #endif
39 
40 
QgsPalettedRendererWidget(QgsRasterLayer * layer,const QgsRectangle & extent)41 QgsPalettedRendererWidget::QgsPalettedRendererWidget( QgsRasterLayer *layer, const QgsRectangle &extent ): QgsRasterRendererWidget( layer, extent )
42 {
43   setupUi( this );
44 
45   mCalculatingProgressBar->hide();
46   mCancelButton->hide();
47 
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() ) );
52 
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 );
60 
61 
62   mButtonAdvanced->setMenu( mAdvancedMenu );
63 
64   mModel = new QgsPalettedRendererModel( this );
65   mProxyModel = new QgsPalettedRendererProxyModel( this );
66   mProxyModel->setSourceModel( mModel );
67   mTreeView->setSortingEnabled( false );
68   mTreeView->setModel( mProxyModel );
69 
70   connect( this, &QgsPalettedRendererWidget::widgetChanged, this, [ = ]
71   {
72     mProxyModel->sort( QgsPalettedRendererModel::Column::ValueColumn );
73   } );
74 
75 #ifdef ENABLE_MODELTEST
76   new ModelTest( mModel, this );
77 #endif
78 
79   mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ColorColumn, new QgsColorSwatchDelegate( this ) );
80   mValueDelegate = new QgsLocaleAwareNumericLineEditDelegate( Qgis::DataType::UnknownDataType, this );
81   mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ValueColumn, mValueDelegate );
82 
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 );
92 
93   connect( mTreeView, &QTreeView::customContextMenuRequested, this, [ = ]( QPoint ) { mContextMenu->exec( QCursor::pos() ); } );
94 
95   btnColorRamp->setShowRandomColorRamp( true );
96 
97   connect( btnColorRamp, &QgsColorRampButton::colorRampChanged, this, &QgsPalettedRendererWidget::applyColorRamp );
98 
99   mBandComboBox->setLayer( mRasterLayer );
100 
101   if ( mRasterLayer )
102   {
103     QgsRasterDataProvider *provider = mRasterLayer->dataProvider();
104     if ( !provider )
105     {
106       return;
107     }
108     setFromRenderer( mRasterLayer->renderer() );
109   }
110 
111   connect( mBandComboBox, &QgsRasterBandComboBox::bandChanged, this, &QgsRasterRendererWidget::widgetChanged );
112   connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
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 );
117 
118   if ( mRasterLayer && mRasterLayer->dataProvider() )
119   {
120     mLoadFromLayerAction->setEnabled( !mRasterLayer->dataProvider()->colorTable( mBandComboBox->currentBand() ).isEmpty() );
121   }
122   else
123   {
124     mLoadFromLayerAction->setEnabled( false );
125   }
126 
127   connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( QgsMapLayer * ) >( &QgsProject::layerWillBeRemoved ), this, &QgsPalettedRendererWidget::layerWillBeRemoved );
128   connect( mBandComboBox, &QgsRasterBandComboBox::bandChanged, this, &QgsPalettedRendererWidget::bandChanged );
129 }
130 
~QgsPalettedRendererWidget()131 QgsPalettedRendererWidget::~QgsPalettedRendererWidget()
132 {
133   if ( mGatherer )
134   {
135     mGatherer->stop();
136     mGatherer->wait(); // mGatherer is deleted when wait completes
137   }
138 }
139 
renderer()140 QgsRasterRenderer *QgsPalettedRendererWidget::renderer()
141 {
142   QgsPalettedRasterRenderer::ClassData classes = mProxyModel->classData();
143   int bandNumber = mBandComboBox->currentBand();
144 
145   QgsPalettedRasterRenderer *r = new QgsPalettedRasterRenderer( mRasterLayer->dataProvider(), bandNumber, classes );
146   if ( !btnColorRamp->isNull() )
147   {
148     r->setSourceColorRamp( btnColorRamp->colorRamp() );
149   }
150   return r;
151 }
152 
setFromRenderer(const QgsRasterRenderer * r)153 void QgsPalettedRendererWidget::setFromRenderer( const QgsRasterRenderer *r )
154 {
155   const QgsPalettedRasterRenderer *pr = dynamic_cast<const QgsPalettedRasterRenderer *>( r );
156   if ( pr )
157   {
158     mBand = pr->band();
159     whileBlocking( mBandComboBox )->setBand( mBand );
160 
161     //read values and colors and fill into tree widget
162     mModel->setClassData( pr->classes() );
163 
164     if ( pr->sourceColorRamp() )
165     {
166       whileBlocking( btnColorRamp )->setColorRamp( pr->sourceColorRamp() );
167     }
168     else
169     {
170       std::unique_ptr< QgsColorRamp > ramp( new QgsRandomColorRamp() );
171       whileBlocking( btnColorRamp )->setColorRamp( ramp.get() );
172     }
173   }
174   else
175   {
176     loadFromLayer();
177     std::unique_ptr< QgsColorRamp > ramp( new QgsRandomColorRamp() );
178     whileBlocking( btnColorRamp )->setColorRamp( ramp.get() );
179   }
180 
181   if ( mRasterLayer && mRasterLayer->dataProvider() )
182   {
183     mValueDelegate->setDataType( mRasterLayer->dataProvider()->dataType( mBand ) );
184   }
185 }
186 
setSelectionColor(const QItemSelection & selection,const QColor & color)187 void QgsPalettedRendererWidget::setSelectionColor( const QItemSelection &selection, const QColor &color )
188 {
189   // don't want to emit widgetChanged multiple times
190   disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
191 
192   QModelIndex colorIndex;
193   const auto constSelection = selection;
194   for ( const QItemSelectionRange &range : constSelection )
195   {
196     const auto constIndexes = range.indexes();
197     for ( const QModelIndex &index : constIndexes )
198     {
199       colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
200       mModel->setData( colorIndex, color, Qt::EditRole );
201     }
202   }
203   connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
204 
205   emit widgetChanged();
206 }
207 
deleteEntry()208 void QgsPalettedRendererWidget::deleteEntry()
209 {
210   // don't want to emit widgetChanged multiple times
211   disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
212 
213   QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
214   const auto constSel = sel;
215   for ( const QItemSelectionRange &range : constSel )
216   {
217     if ( range.isValid() )
218       mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
219   }
220 
221   connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
222 
223   emit widgetChanged();
224 }
225 
addEntry()226 void QgsPalettedRendererWidget::addEntry()
227 {
228   disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
229 
230   QColor color( 150, 150, 150 );
231   std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
232   if ( ramp )
233   {
234     color = ramp->color( 1.0 );
235   }
236   QModelIndex newEntry = mModel->addEntry( color );
237   mTreeView->scrollTo( newEntry );
238   mTreeView->selectionModel()->select( mProxyModel->mapFromSource( newEntry ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
239   connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
240   emit widgetChanged();
241 }
242 
changeColor()243 void QgsPalettedRendererWidget::changeColor()
244 {
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>();
248 
249   QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast< QWidget * >( parent() ) );
250   if ( panel && panel->dockMode() )
251   {
252     QgsCompoundColorWidget *colorWidget = new QgsCompoundColorWidget( panel, currentColor, QgsCompoundColorWidget::LayoutVertical );
253     colorWidget->setPanelTitle( tr( "Select Color" ) );
254     colorWidget->setAllowOpacity( true );
255     connect( colorWidget, &QgsCompoundColorWidget::currentColorChanged, this, [ = ]( const QColor & color ) { setSelectionColor( sel, color ); } );
256     panel->openPanel( colorWidget );
257   }
258   else
259   {
260     // modal dialog version... yuck
261     QColor newColor = QgsColorDialog::getColor( currentColor, this, QStringLiteral( "Change color" ), true );
262     if ( newColor.isValid() )
263     {
264       setSelectionColor( sel, newColor );
265     }
266   }
267 }
268 
changeOpacity()269 void QgsPalettedRendererWidget::changeOpacity()
270 {
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>();
274 
275   bool ok;
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 );
278   if ( ok )
279   {
280     int newOpacity = opacity / 100 * 255;
281 
282     // don't want to emit widgetChanged multiple times
283     disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
284 
285     const auto constSel = sel;
286     for ( const QItemSelectionRange &range : constSel )
287     {
288       const auto constIndexes = range.indexes();
289       for ( const QModelIndex &index : constIndexes )
290       {
291         colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
292 
293         QColor newColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
294         newColor.setAlpha( newOpacity );
295         mModel->setData( colorIndex, newColor, Qt::EditRole );
296       }
297     }
298     connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
299 
300     emit widgetChanged();
301   }
302 }
303 
changeLabel()304 void QgsPalettedRendererWidget::changeLabel()
305 {
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();
309 
310   bool ok;
311   QString newLabel = QInputDialog::getText( this, tr( "Label" ), tr( "Change label" ), QLineEdit::Normal, currentLabel, &ok );
312   if ( ok )
313   {
314     // don't want to emit widgetChanged multiple times
315     disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
316 
317     const auto constSel = sel;
318     for ( const QItemSelectionRange &range : constSel )
319     {
320       const auto constIndexes = range.indexes();
321       for ( const QModelIndex &index : constIndexes )
322       {
323         labelIndex = mModel->index( index.row(), QgsPalettedRendererModel::LabelColumn );
324         mModel->setData( labelIndex, newLabel, Qt::EditRole );
325       }
326     }
327     connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
328 
329     emit widgetChanged();
330   }
331 }
332 
applyColorRamp()333 void QgsPalettedRendererWidget::applyColorRamp()
334 {
335   std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
336   if ( !ramp )
337   {
338     return;
339   }
340 
341   disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
342 
343   QgsPalettedRasterRenderer::ClassData data = mProxyModel->classData();
344   QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
345 
346   double numberOfEntries = data.count();
347   int i = 0;
348 
349   if ( QgsRandomColorRamp *randomRamp = dynamic_cast<QgsRandomColorRamp *>( ramp.get() ) )
350   {
351     //ramp is a random colors ramp, so inform it of the total number of required colors
352     //this allows the ramp to pregenerate a set of visually distinctive colors
353     randomRamp->setTotalColorCount( numberOfEntries );
354   }
355 
356   if ( numberOfEntries > 1 )
357     numberOfEntries -= 1; //avoid duplicate first color
358 
359   for ( ; cIt != data.end(); ++cIt )
360   {
361     cIt->color = ramp->color( i / numberOfEntries );
362     i++;
363   }
364   mModel->setClassData( data );
365 
366   connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
367   emit widgetChanged();
368 }
369 
loadColorTable()370 void QgsPalettedRendererWidget::loadColorTable()
371 {
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() )
376   {
377     QgsPalettedRasterRenderer::ClassData classes = QgsPalettedRasterRenderer::classDataFromFile( fileName );
378     if ( !classes.isEmpty() )
379     {
380       disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
381       mModel->setClassData( classes );
382       emit widgetChanged();
383       connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
384     }
385     else
386     {
387       QMessageBox::critical( nullptr, tr( "Load Color Table" ), tr( "Could not interpret file as a raster color table." ) );
388     }
389   }
390 }
391 
saveColorTable()392 void QgsPalettedRendererWidget::saveColorTable()
393 {
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() )
398   {
399     if ( !fileName.endsWith( QLatin1String( ".clr" ), Qt::CaseInsensitive ) )
400     {
401       fileName = fileName + ".clr";
402     }
403 
404     QFile outputFile( fileName );
405     if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
406     {
407       QTextStream outputStream( &outputFile );
408       outputStream << QgsPalettedRasterRenderer::classDataToString( mProxyModel->classData() );
409       outputStream.flush();
410       outputFile.close();
411 
412       QFileInfo fileInfo( fileName );
413       settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
414     }
415     else
416     {
417       QMessageBox::warning( this, tr( "Save Color Table as File" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
418     }
419   }
420 }
421 
classify()422 void QgsPalettedRendererWidget::classify()
423 {
424   if ( mRasterLayer )
425   {
426     QgsRasterDataProvider *provider = mRasterLayer->dataProvider();
427     if ( !provider )
428     {
429       return;
430     }
431 
432     if ( mGatherer )
433     {
434       mGatherer->stop();
435       return;
436     }
437 
438     mGatherer = new QgsPalettedRendererClassGatherer( mRasterLayer, mBandComboBox->currentBand(), mModel->classData(), btnColorRamp->colorRamp() );
439 
440     connect( mGatherer, &QgsPalettedRendererClassGatherer::progressChanged, mCalculatingProgressBar, [ = ]( int progress )
441     {
442       mCalculatingProgressBar->setValue( progress );
443     } );
444 
445     mCalculatingProgressBar->show();
446     mCancelButton->show();
447     connect( mCancelButton, &QPushButton::clicked, mGatherer, &QgsPalettedRendererClassGatherer::stop );
448 
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 );
453     mGatherer->start();
454   }
455 }
456 
loadFromLayer()457 void QgsPalettedRendererWidget::loadFromLayer()
458 {
459   //read default palette settings from layer
460   QgsRasterDataProvider *provider = mRasterLayer->dataProvider();
461   if ( provider )
462   {
463     QList<QgsColorRampShader::ColorRampItem> table = provider->colorTable( mBandComboBox->currentBand() );
464     if ( !table.isEmpty() )
465     {
466       QgsPalettedRasterRenderer::ClassData classes = QgsPalettedRasterRenderer::colorTableToClassData( provider->colorTable( mBandComboBox->currentBand() ) );
467       mModel->setClassData( classes );
468       emit widgetChanged();
469     }
470   }
471 }
472 
bandChanged(int band)473 void QgsPalettedRendererWidget::bandChanged( int band )
474 {
475   if ( band == mBand )
476     return;
477 
478   if ( mRasterLayer && mRasterLayer->dataProvider( ) )
479   {
480     mValueDelegate->setDataType( mRasterLayer->dataProvider( )->dataType( mBand ) );
481   }
482 
483   bool deleteExisting = false;
484   if ( !mModel->classData().isEmpty() )
485   {
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 );
491 
492     deleteExisting = ( res == QMessageBox::Yes );
493   }
494 
495   mBand = band;
496   mModel->blockSignals( true );
497   if ( deleteExisting )
498     mModel->deleteAll();
499 
500   mModel->blockSignals( false );
501   emit widgetChanged();
502 }
503 
gatheredClasses()504 void QgsPalettedRendererWidget::gatheredClasses()
505 {
506   if ( !mGatherer || mGatherer->wasCanceled() )
507     return;
508 
509   mModel->setClassData( mGatherer->classes() );
510   emit widgetChanged();
511 }
512 
gathererThreadFinished()513 void QgsPalettedRendererWidget::gathererThreadFinished()
514 {
515   mGatherer->deleteLater();
516   mGatherer = nullptr;
517   mClassifyButton->setText( tr( "Classify" ) );
518   mClassifyButton->setEnabled( true );
519   mCalculatingProgressBar->hide();
520   mCancelButton->hide();
521 }
522 
layerWillBeRemoved(QgsMapLayer * layer)523 void QgsPalettedRendererWidget::layerWillBeRemoved( QgsMapLayer *layer )
524 {
525   if ( mGatherer && mRasterLayer == layer )
526   {
527     mGatherer->stop();
528     mGatherer->wait();
529   }
530 }
531 
532 //
533 // QgsPalettedRendererModel
534 //
535 
536 ///@cond PRIVATE
QgsPalettedRendererModel(QObject * parent)537 QgsPalettedRendererModel::QgsPalettedRendererModel( QObject *parent )
538   : QAbstractItemModel( parent )
539 {
540 
541 }
542 
setClassData(const QgsPalettedRasterRenderer::ClassData & data)543 void QgsPalettedRendererModel::setClassData( const QgsPalettedRasterRenderer::ClassData &data )
544 {
545   beginResetModel();
546   mData = data;
547   endResetModel();
548 }
549 
index(int row,int column,const QModelIndex & parent) const550 QModelIndex QgsPalettedRendererModel::index( int row, int column, const QModelIndex &parent ) const
551 {
552   if ( column < 0 || column >= columnCount() )
553   {
554     //column out of bounds
555     return QModelIndex();
556   }
557 
558   if ( !parent.isValid() && row >= 0 && row < mData.size() )
559   {
560     //return an index for the item at this position
561     return createIndex( row, column );
562   }
563 
564   //only top level supported
565   return QModelIndex();
566 }
567 
parent(const QModelIndex & index) const568 QModelIndex QgsPalettedRendererModel::parent( const QModelIndex &index ) const
569 {
570   Q_UNUSED( index )
571 
572   //all items are top level
573   return QModelIndex();
574 }
575 
columnCount(const QModelIndex & parent) const576 int QgsPalettedRendererModel::columnCount( const QModelIndex &parent ) const
577 {
578   if ( parent.isValid() )
579     return 0;
580 
581   return 3;
582 }
583 
rowCount(const QModelIndex & parent) const584 int QgsPalettedRendererModel::rowCount( const QModelIndex &parent ) const
585 {
586   if ( parent.isValid() )
587     return 0;
588 
589   return mData.count();
590 }
591 
data(const QModelIndex & index,int role) const592 QVariant QgsPalettedRendererModel::data( const QModelIndex &index, int role ) const
593 {
594   if ( !index.isValid() )
595     return QVariant();
596 
597   switch ( role )
598   {
599     case Qt::DisplayRole:
600     case Qt::EditRole:
601     {
602       switch ( index.column() )
603       {
604         case ValueColumn:
605           return mData.at( index.row() ).value;
606 
607         case ColorColumn:
608           return mData.at( index.row() ).color;
609 
610         case LabelColumn:
611           return mData.at( index.row() ).label;
612       }
613     }
614 
615     default:
616       break;
617   }
618 
619   return QVariant();
620 }
621 
headerData(int section,Qt::Orientation orientation,int role) const622 QVariant QgsPalettedRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
623 {
624   switch ( orientation )
625   {
626     case Qt::Vertical:
627       return QVariant();
628 
629     case Qt::Horizontal:
630     {
631       switch ( role )
632       {
633         case Qt::DisplayRole:
634         {
635           switch ( section )
636           {
637             case ValueColumn:
638               return tr( "Value" );
639 
640             case ColorColumn:
641               return tr( "Color" );
642 
643             case LabelColumn:
644               return tr( "Label" );
645           }
646         }
647 
648       }
649       break;
650     }
651 
652     default:
653       return QAbstractItemModel::headerData( section, orientation, role );
654   }
655   return QAbstractItemModel::headerData( section, orientation, role );
656 }
657 
setData(const QModelIndex & index,const QVariant & value,int)658 bool QgsPalettedRendererModel::setData( const QModelIndex &index, const QVariant &value, int )
659 {
660   if ( !index.isValid() )
661     return false;
662   if ( index.row() >= mData.length() )
663     return false;
664 
665   switch ( index.column() )
666   {
667     case ValueColumn:
668     {
669       bool ok = false;
670       double newValue = value.toDouble( &ok );
671       if ( !ok )
672         return false;
673 
674       mData[ index.row() ].value = newValue;
675       emit dataChanged( index, index );
676       emit classesChanged();
677       return true;
678     }
679 
680     case ColorColumn:
681     {
682       mData[ index.row() ].color = value.value<QColor>();
683       emit dataChanged( index, index );
684       emit classesChanged();
685       return true;
686     }
687 
688     case LabelColumn:
689     {
690       mData[ index.row() ].label = value.toString();
691       emit dataChanged( index, index );
692       emit classesChanged();
693       return true;
694     }
695   }
696 
697   return false;
698 }
699 
flags(const QModelIndex & index) const700 Qt::ItemFlags QgsPalettedRendererModel::flags( const QModelIndex &index ) const
701 {
702   if ( !index.isValid() )
703     return QAbstractItemModel::flags( index ) | Qt::ItemIsDropEnabled;
704 
705   Qt::ItemFlags f = QAbstractItemModel::flags( index );
706   switch ( index.column() )
707   {
708     case ValueColumn:
709     case LabelColumn:
710     case ColorColumn:
711       f = f | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
712       break;
713   }
714   return f | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
715 }
716 
removeRows(int row,int count,const QModelIndex & parent)717 bool QgsPalettedRendererModel::removeRows( int row, int count, const QModelIndex &parent )
718 {
719   if ( row < 0 || row >= mData.count() )
720     return false;
721   if ( parent.isValid() )
722     return false;
723 
724   for ( int i = row + count - 1; i >= row; --i )
725   {
726     beginRemoveRows( parent, i, i );
727     mData.removeAt( i );
728     endRemoveRows();
729   }
730   emit classesChanged();
731   return true;
732 }
733 
insertRows(int row,int count,const QModelIndex &)734 bool QgsPalettedRendererModel::insertRows( int row, int count, const QModelIndex & )
735 {
736   QgsPalettedRasterRenderer::ClassData::const_iterator cIt = mData.constBegin();
737   int currentMaxValue = -std::numeric_limits<int>::max();
738   for ( ; cIt != mData.constEnd(); ++cIt )
739   {
740     int value = cIt->value;
741     currentMaxValue = std::max( value, currentMaxValue );
742   }
743   int nextValue = std::max( 0, currentMaxValue + 1 );
744 
745   beginInsertRows( QModelIndex(), row, row + count - 1 );
746   for ( int i = row; i < row + count; ++i, ++nextValue )
747   {
748     mData.insert( i, QgsPalettedRasterRenderer::Class( nextValue, QColor( 200, 200, 200 ), QLocale().toString( nextValue ) ) );
749   }
750   endInsertRows();
751   emit classesChanged();
752   return true;
753 }
754 
supportedDropActions() const755 Qt::DropActions QgsPalettedRendererModel::supportedDropActions() const
756 {
757   return Qt::MoveAction;
758 }
759 
mimeTypes() const760 QStringList QgsPalettedRendererModel::mimeTypes() const
761 {
762   QStringList types;
763   types << QStringLiteral( "application/x-qgspalettedrenderermodel" );
764   return types;
765 }
766 
mimeData(const QModelIndexList & indexes) const767 QMimeData *QgsPalettedRendererModel::mimeData( const QModelIndexList &indexes ) const
768 {
769   QMimeData *mimeData = new QMimeData();
770   QByteArray encodedData;
771 
772   QDataStream stream( &encodedData, QIODevice::WriteOnly );
773 
774   // Create list of rows
775   const auto constIndexes = indexes;
776   for ( const QModelIndex &index : constIndexes )
777   {
778     if ( !index.isValid() || index.column() != 0 )
779       continue;
780 
781     stream << index.row();
782   }
783   mimeData->setData( QStringLiteral( "application/x-qgspalettedrenderermodel" ), encodedData );
784   return mimeData;
785 }
786 
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex &)787 bool QgsPalettedRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex & )
788 {
789   Q_UNUSED( column )
790   if ( action != Qt::MoveAction ) return true;
791 
792   if ( !data->hasFormat( QStringLiteral( "application/x-qgspalettedrenderermodel" ) ) )
793     return false;
794 
795   QByteArray encodedData = data->data( QStringLiteral( "application/x-qgspalettedrenderermodel" ) );
796   QDataStream stream( &encodedData, QIODevice::ReadOnly );
797 
798   QVector<int> rows;
799   while ( !stream.atEnd() )
800   {
801     int r;
802     stream >> r;
803     rows.append( r );
804   }
805 
806   QgsPalettedRasterRenderer::ClassData newData;
807   for ( int i = 0; i < rows.count(); ++i )
808     newData << mData.at( rows.at( i ) );
809 
810   if ( row < 0 )
811     row = mData.count();
812 
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 ) );
816   endInsertRows();
817   emit classesChanged();
818   return true;
819 }
820 
addEntry(const QColor & color)821 QModelIndex QgsPalettedRendererModel::addEntry( const QColor &color )
822 {
823   insertRow( rowCount() );
824   QModelIndex newRow = index( mData.count() - 1, 1 );
825   setData( newRow, color );
826   return newRow;
827 }
828 
deleteAll()829 void QgsPalettedRendererModel::deleteAll()
830 {
831   beginResetModel();
832   mData.clear();
833   endResetModel();
834   emit classesChanged();
835 }
836 
run()837 void QgsPalettedRendererClassGatherer::run()
838 {
839   mWasCanceled = false;
840 
841   // allow responsive cancellation
842   mFeedback = new QgsRasterBlockFeedback();
843   connect( mFeedback, &QgsRasterBlockFeedback::progressChanged, this, &QgsPalettedRendererClassGatherer::progressChanged );
844 
845   QgsPalettedRasterRenderer::ClassData newClasses = QgsPalettedRasterRenderer::classDataFromRaster( mLayer->dataProvider(), mBandNumber, mRamp.get(), mFeedback );
846 
847   // combine existing classes with new classes
848   QgsPalettedRasterRenderer::ClassData::iterator classIt = newClasses.begin();
849   emit progressChanged( 0 );
850   qlonglong i = 0;
851   for ( ; classIt != newClasses.end(); ++classIt )
852   {
853     // check if existing classes contains this same class
854     for ( const QgsPalettedRasterRenderer::Class &existingClass : std::as_const( mClasses ) )
855     {
856       if ( existingClass.value == classIt->value )
857       {
858         classIt->color = existingClass.color;
859         classIt->label = existingClass.label;
860         break;
861       }
862     }
863     i ++;
864     emit progressChanged( 100 * ( i / static_cast<float>( newClasses.count() ) ) );
865   }
866   mClasses = newClasses;
867 
868   // be overly cautious - it's *possible* stop() might be called between deleting mFeedback and nulling it
869   mFeedbackMutex.lock();
870   delete mFeedback;
871   mFeedback = nullptr;
872   mFeedbackMutex.unlock();
873 
874   emit collectedClasses();
875 }
876 
877 
classData() const878 QgsPalettedRasterRenderer::ClassData QgsPalettedRendererProxyModel::classData() const
879 {
880   QgsPalettedRasterRenderer::ClassData data;
881   for ( int i = 0; i < rowCount( ); ++i )
882   {
883     data.push_back( qobject_cast<QgsPalettedRendererModel *>( sourceModel() )->classAtIndex( mapToSource( index( i, 0 ) ) ) );
884   }
885   return data;
886 }
887 
888 
889 ///@endcond PRIVATE
890