QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgspalettedrendererwidget.cpp
Go to the documentation of this file.
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 
20 #include "qgsrasterdataprovider.h"
21 #include "qgsrasterlayer.h"
22 #include "qgscolordialog.h"
23 #include "qgssettings.h"
24 #include "qgsproject.h"
26 #include "qgscolorrampimpl.h"
28 
29 #include <QColorDialog>
30 #include <QInputDialog>
31 #include <QFileDialog>
32 #include <QMessageBox>
33 #include <QMenu>
34 #include <QMimeData>
35 #include <QTextStream>
36 
37 #ifdef ENABLE_MODELTEST
38 #include "modeltest.h"
39 #endif
40 
41 
43 {
44  setupUi( this );
45 
46  mCalculatingProgressBar->hide();
47  mCancelButton->hide();
48 
49  mContextMenu = new QMenu( tr( "Options" ), this );
50  mContextMenu->addAction( tr( "Change Color…" ), this, SLOT( changeColor() ) );
51  mContextMenu->addAction( tr( "Change Opacity…" ), this, SLOT( changeOpacity() ) );
52  mContextMenu->addAction( tr( "Change Label…" ), this, SLOT( changeLabel() ) );
53 
54  mAdvancedMenu = new QMenu( tr( "Advanced Options" ), this );
55  QAction *mLoadFromLayerAction = mAdvancedMenu->addAction( tr( "Load Classes from Layer" ) );
56  connect( mLoadFromLayerAction, &QAction::triggered, this, &QgsPalettedRendererWidget::loadFromLayer );
57  QAction *loadFromFile = mAdvancedMenu->addAction( tr( "Load Color Map from File…" ) );
58  connect( loadFromFile, &QAction::triggered, this, &QgsPalettedRendererWidget::loadColorTable );
59  QAction *exportToFile = mAdvancedMenu->addAction( tr( "Export Color Map to File…" ) );
60  connect( exportToFile, &QAction::triggered, this, &QgsPalettedRendererWidget::saveColorTable );
61 
62 
63  mButtonAdvanced->setMenu( mAdvancedMenu );
64 
65  mModel = new QgsPalettedRendererModel( this );
66  mProxyModel = new QgsPalettedRendererProxyModel( this );
67  mProxyModel->setSourceModel( mModel );
68  mTreeView->setSortingEnabled( false );
69  mTreeView->setModel( mProxyModel );
70 
71  connect( this, &QgsPalettedRendererWidget::widgetChanged, this, [ = ]
72  {
73  mProxyModel->sort( QgsPalettedRendererModel::Column::ValueColumn );
74  } );
75 
76 #ifdef ENABLE_MODELTEST
77  new ModelTest( mModel, this );
78 #endif
79 
80  mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ColorColumn, new QgsColorSwatchDelegate( this ) );
81  mValueDelegate = new QgsLocaleAwareNumericLineEditDelegate( Qgis::DataType::UnknownDataType, this );
82  mTreeView->setItemDelegateForColumn( QgsPalettedRendererModel::ValueColumn, mValueDelegate );
83 
84  mTreeView->setColumnWidth( QgsPalettedRendererModel::ColorColumn, Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 6.6 );
85  mTreeView->setContextMenuPolicy( Qt::CustomContextMenu );
86  mTreeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
87  mTreeView->setDragEnabled( true );
88  mTreeView->setAcceptDrops( true );
89  mTreeView->setDropIndicatorShown( true );
90  mTreeView->setDragDropMode( QAbstractItemView::InternalMove );
91  mTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
92  mTreeView->setDefaultDropAction( Qt::MoveAction );
93 
94  connect( mTreeView, &QTreeView::customContextMenuRequested, this, [ = ]( QPoint ) { mContextMenu->exec( QCursor::pos() ); } );
95 
96  btnColorRamp->setShowRandomColorRamp( true );
97 
98  connect( btnColorRamp, &QgsColorRampButton::colorRampChanged, this, &QgsPalettedRendererWidget::applyColorRamp );
99 
100  mBandComboBox->setLayer( mRasterLayer );
101 
102  if ( mRasterLayer )
103  {
105  if ( !provider )
106  {
107  return;
108  }
110  }
111 
113  connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
114  connect( mDeleteEntryButton, &QPushButton::clicked, this, &QgsPalettedRendererWidget::deleteEntry );
115  connect( mButtonDeleteAll, &QPushButton::clicked, mModel, &QgsPalettedRendererModel::deleteAll );
116  connect( mAddEntryButton, &QPushButton::clicked, this, &QgsPalettedRendererWidget::addEntry );
117  connect( mClassifyButton, &QPushButton::clicked, this, &QgsPalettedRendererWidget::classify );
118 
120  {
121  mLoadFromLayerAction->setEnabled( !mRasterLayer->dataProvider()->colorTable( mBandComboBox->currentBand() ).isEmpty() );
122  }
123  else
124  {
125  mLoadFromLayerAction->setEnabled( false );
126  }
127 
128  connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( QgsMapLayer * ) >( &QgsProject::layerWillBeRemoved ), this, &QgsPalettedRendererWidget::layerWillBeRemoved );
129  connect( mBandComboBox, &QgsRasterBandComboBox::bandChanged, this, &QgsPalettedRendererWidget::bandChanged );
130 }
131 
133 {
134  if ( mGatherer )
135  {
136  mGatherer->stop();
137  mGatherer->wait(); // mGatherer is deleted when wait completes
138  }
139 }
140 
142 {
143  QgsPalettedRasterRenderer::ClassData classes = mProxyModel->classData();
144  int bandNumber = mBandComboBox->currentBand();
145 
147  if ( !btnColorRamp->isNull() )
148  {
149  r->setSourceColorRamp( btnColorRamp->colorRamp() );
150  }
151  return r;
152 }
153 
155 {
156  const QgsPalettedRasterRenderer *pr = dynamic_cast<const QgsPalettedRasterRenderer *>( r );
157  if ( pr )
158  {
159  mBand = pr->band();
160  whileBlocking( mBandComboBox )->setBand( mBand );
161 
162  //read values and colors and fill into tree widget
163  mModel->setClassData( pr->classes() );
164 
165  if ( pr->sourceColorRamp() )
166  {
167  whileBlocking( btnColorRamp )->setColorRamp( pr->sourceColorRamp() );
168  }
169  else
170  {
171  std::unique_ptr< QgsColorRamp > ramp( new QgsRandomColorRamp() );
172  whileBlocking( btnColorRamp )->setColorRamp( ramp.get() );
173  }
174  }
175  else
176  {
177  loadFromLayer();
178  std::unique_ptr< QgsColorRamp > ramp( new QgsRandomColorRamp() );
179  whileBlocking( btnColorRamp )->setColorRamp( ramp.get() );
180  }
181 
183  {
184  mValueDelegate->setDataType( mRasterLayer->dataProvider()->dataType( mBand ) );
185  }
186 }
187 
188 void QgsPalettedRendererWidget::setSelectionColor( const QItemSelection &selection, const QColor &color )
189 {
190  // don't want to emit widgetChanged multiple times
191  disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
192 
193  QModelIndex colorIndex;
194  const auto constSelection = selection;
195  for ( const QItemSelectionRange &range : constSelection )
196  {
197  const auto constIndexes = range.indexes();
198  for ( const QModelIndex &index : constIndexes )
199  {
200  colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
201  mModel->setData( colorIndex, color, Qt::EditRole );
202  }
203  }
204  connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
205 
206  emit widgetChanged();
207 }
208 
209 void QgsPalettedRendererWidget::deleteEntry()
210 {
211  // don't want to emit widgetChanged multiple times
212  disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
213 
214  QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
215  const auto constSel = sel;
216  for ( const QItemSelectionRange &range : constSel )
217  {
218  if ( range.isValid() )
219  mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() );
220  }
221 
222  connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
223 
224  emit widgetChanged();
225 }
226 
227 void QgsPalettedRendererWidget::addEntry()
228 {
229  disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
230 
231  QColor color( 150, 150, 150 );
232  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
233  if ( ramp )
234  {
235  color = ramp->color( 1.0 );
236  }
237  QModelIndex newEntry = mModel->addEntry( color );
238  mTreeView->scrollTo( newEntry );
239  mTreeView->selectionModel()->select( mProxyModel->mapFromSource( newEntry ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
240  connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
241  emit widgetChanged();
242 }
243 
244 void QgsPalettedRendererWidget::changeColor()
245 {
246  QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
247  QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
248  QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
249 
250  QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( qobject_cast< QWidget * >( parent() ) );
251  if ( panel && panel->dockMode() )
252  {
254  colorWidget->setPanelTitle( tr( "Select Color" ) );
255  colorWidget->setAllowOpacity( true );
256  connect( colorWidget, &QgsCompoundColorWidget::currentColorChanged, this, [ = ]( const QColor & color ) { setSelectionColor( sel, color ); } );
257  panel->openPanel( colorWidget );
258  }
259  else
260  {
261  // modal dialog version... yuck
262  QColor newColor = QgsColorDialog::getColor( currentColor, this, QStringLiteral( "Change color" ), true );
263  if ( newColor.isValid() )
264  {
265  setSelectionColor( sel, newColor );
266  }
267  }
268 }
269 
270 void QgsPalettedRendererWidget::changeOpacity()
271 {
272  QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
273  QModelIndex colorIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::ColorColumn );
274  QColor currentColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
275 
276  bool ok;
277  double oldOpacity = ( currentColor.alpha() / 255.0 ) * 100.0;
278  double opacity = QInputDialog::getDouble( this, tr( "Opacity" ), tr( "Change color opacity [%]" ), oldOpacity, 0.0, 100.0, 0, &ok );
279  if ( ok )
280  {
281  int newOpacity = opacity / 100 * 255;
282 
283  // don't want to emit widgetChanged multiple times
284  disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
285 
286  const auto constSel = sel;
287  for ( const QItemSelectionRange &range : constSel )
288  {
289  const auto constIndexes = range.indexes();
290  for ( const QModelIndex &index : constIndexes )
291  {
292  colorIndex = mModel->index( index.row(), QgsPalettedRendererModel::ColorColumn );
293 
294  QColor newColor = mModel->data( colorIndex, Qt::DisplayRole ).value<QColor>();
295  newColor.setAlpha( newOpacity );
296  mModel->setData( colorIndex, newColor, Qt::EditRole );
297  }
298  }
299  connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
300 
301  emit widgetChanged();
302  }
303 }
304 
305 void QgsPalettedRendererWidget::changeLabel()
306 {
307  QItemSelection sel = mProxyModel->mapSelectionToSource( mTreeView->selectionModel()->selection() );
308  QModelIndex labelIndex = mModel->index( sel.first().top(), QgsPalettedRendererModel::LabelColumn );
309  QString currentLabel = mModel->data( labelIndex, Qt::DisplayRole ).toString();
310 
311  bool ok;
312  QString newLabel = QInputDialog::getText( this, tr( "Label" ), tr( "Change label" ), QLineEdit::Normal, currentLabel, &ok );
313  if ( ok )
314  {
315  // don't want to emit widgetChanged multiple times
316  disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
317 
318  const auto constSel = sel;
319  for ( const QItemSelectionRange &range : constSel )
320  {
321  const auto constIndexes = range.indexes();
322  for ( const QModelIndex &index : constIndexes )
323  {
324  labelIndex = mModel->index( index.row(), QgsPalettedRendererModel::LabelColumn );
325  mModel->setData( labelIndex, newLabel, Qt::EditRole );
326  }
327  }
328  connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
329 
330  emit widgetChanged();
331  }
332 }
333 
334 void QgsPalettedRendererWidget::applyColorRamp()
335 {
336  std::unique_ptr< QgsColorRamp > ramp( btnColorRamp->colorRamp() );
337  if ( !ramp )
338  {
339  return;
340  }
341 
342  disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
343 
344  QgsPalettedRasterRenderer::ClassData data = mProxyModel->classData();
345  QgsPalettedRasterRenderer::ClassData::iterator cIt = data.begin();
346 
347  double numberOfEntries = data.count();
348  int i = 0;
349 
350  if ( QgsRandomColorRamp *randomRamp = dynamic_cast<QgsRandomColorRamp *>( ramp.get() ) )
351  {
352  //ramp is a random colors ramp, so inform it of the total number of required colors
353  //this allows the ramp to pregenerate a set of visually distinctive colors
354  randomRamp->setTotalColorCount( numberOfEntries );
355  }
356 
357  if ( numberOfEntries > 1 )
358  numberOfEntries -= 1; //avoid duplicate first color
359 
360  for ( ; cIt != data.end(); ++cIt )
361  {
362  cIt->color = ramp->color( i / numberOfEntries );
363  i++;
364  }
365  mModel->setClassData( data );
366 
367  connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
368  emit widgetChanged();
369 }
370 
371 void QgsPalettedRendererWidget::loadColorTable()
372 {
373  QgsSettings settings;
374  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
375  QString fileName = QFileDialog::getOpenFileName( this, tr( "Load Color Table from File" ), lastDir );
376  if ( !fileName.isEmpty() )
377  {
379  if ( !classes.isEmpty() )
380  {
381  disconnect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
382  mModel->setClassData( classes );
383  emit widgetChanged();
384  connect( mModel, &QgsPalettedRendererModel::classesChanged, this, &QgsPalettedRendererWidget::widgetChanged );
385  }
386  else
387  {
388  QMessageBox::critical( nullptr, tr( "Load Color Table" ), tr( "Could not interpret file as a raster color table." ) );
389  }
390  }
391 }
392 
393 void QgsPalettedRendererWidget::saveColorTable()
394 {
395  QgsSettings settings;
396  QString lastDir = settings.value( QStringLiteral( "lastColorMapDir" ), QDir::homePath() ).toString();
397  QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Color Table as File" ), lastDir, tr( "Text (*.clr)" ) );
398  if ( !fileName.isEmpty() )
399  {
400  if ( !fileName.endsWith( QLatin1String( ".clr" ), Qt::CaseInsensitive ) )
401  {
402  fileName = fileName + ".clr";
403  }
404 
405  QFile outputFile( fileName );
406  if ( outputFile.open( QFile::WriteOnly | QIODevice::Truncate ) )
407  {
408  QTextStream outputStream( &outputFile );
409  outputStream << QgsPalettedRasterRenderer::classDataToString( mProxyModel->classData() );
410  outputStream.flush();
411  outputFile.close();
412 
413  QFileInfo fileInfo( fileName );
414  settings.setValue( QStringLiteral( "lastColorMapDir" ), fileInfo.absoluteDir().absolutePath() );
415  }
416  else
417  {
418  QMessageBox::warning( this, tr( "Save Color Table as File" ), tr( "Write access denied. Adjust the file permissions and try again.\n\n" ) );
419  }
420  }
421 }
422 
423 void QgsPalettedRendererWidget::classify()
424 {
425  if ( mRasterLayer )
426  {
428  if ( !provider )
429  {
430  return;
431  }
432 
433  if ( mGatherer )
434  {
435  mGatherer->stop();
436  return;
437  }
438 
439  mGatherer = new QgsPalettedRendererClassGatherer( mRasterLayer, mBandComboBox->currentBand(), mModel->classData(), btnColorRamp->colorRamp() );
440 
441  connect( mGatherer, &QgsPalettedRendererClassGatherer::progressChanged, mCalculatingProgressBar, [ = ]( int progress )
442  {
443  mCalculatingProgressBar->setValue( progress );
444  } );
445 
446  mCalculatingProgressBar->show();
447  mCancelButton->show();
448  connect( mCancelButton, &QPushButton::clicked, mGatherer, &QgsPalettedRendererClassGatherer::stop );
449 
450  connect( mGatherer, &QgsPalettedRendererClassGatherer::collectedClasses, this, &QgsPalettedRendererWidget::gatheredClasses );
451  connect( mGatherer, &QgsPalettedRendererClassGatherer::finished, this, &QgsPalettedRendererWidget::gathererThreadFinished );
452  mClassifyButton->setText( tr( "Calculating…" ) );
453  mClassifyButton->setEnabled( false );
454  mGatherer->start();
455  }
456 }
457 
458 void QgsPalettedRendererWidget::loadFromLayer()
459 {
460  //read default palette settings from layer
462  if ( provider )
463  {
464  QList<QgsColorRampShader::ColorRampItem> table = provider->colorTable( mBandComboBox->currentBand() );
465  if ( !table.isEmpty() )
466  {
467  QgsPalettedRasterRenderer::ClassData classes = QgsPalettedRasterRenderer::colorTableToClassData( provider->colorTable( mBandComboBox->currentBand() ) );
468  mModel->setClassData( classes );
469  emit widgetChanged();
470  }
471  }
472 }
473 
474 void QgsPalettedRendererWidget::bandChanged( int band )
475 {
476  if ( band == mBand )
477  return;
478 
480  {
481  mValueDelegate->setDataType( mRasterLayer->dataProvider( )->dataType( mBand ) );
482  }
483 
484  bool deleteExisting = false;
485  if ( !mModel->classData().isEmpty() )
486  {
487  int res = QMessageBox::question( this,
488  tr( "Delete Classification" ),
489  tr( "The classification band was changed from %1 to %2.\n"
490  "Should the existing classes be deleted?" ).arg( mBand ).arg( band ),
491  QMessageBox::Yes | QMessageBox::No );
492 
493  deleteExisting = ( res == QMessageBox::Yes );
494  }
495 
496  mBand = band;
497  mModel->blockSignals( true );
498  if ( deleteExisting )
499  mModel->deleteAll();
500 
501  mModel->blockSignals( false );
502  emit widgetChanged();
503 }
504 
505 void QgsPalettedRendererWidget::gatheredClasses()
506 {
507  if ( !mGatherer || mGatherer->wasCanceled() )
508  return;
509 
510  mModel->setClassData( mGatherer->classes() );
511  emit widgetChanged();
512 }
513 
514 void QgsPalettedRendererWidget::gathererThreadFinished()
515 {
516  mGatherer->deleteLater();
517  mGatherer = nullptr;
518  mClassifyButton->setText( tr( "Classify" ) );
519  mClassifyButton->setEnabled( true );
520  mCalculatingProgressBar->hide();
521  mCancelButton->hide();
522 }
523 
524 void QgsPalettedRendererWidget::layerWillBeRemoved( QgsMapLayer *layer )
525 {
526  if ( mGatherer && mRasterLayer == layer )
527  {
528  mGatherer->stop();
529  mGatherer->wait();
530  }
531 }
532 
533 //
534 // QgsPalettedRendererModel
535 //
536 
538 QgsPalettedRendererModel::QgsPalettedRendererModel( QObject *parent )
539  : QAbstractItemModel( parent )
540 {
541 
542 }
543 
544 void QgsPalettedRendererModel::setClassData( const QgsPalettedRasterRenderer::ClassData &data )
545 {
546  beginResetModel();
547  mData = data;
548  endResetModel();
549 }
550 
551 QModelIndex QgsPalettedRendererModel::index( int row, int column, const QModelIndex &parent ) const
552 {
553  if ( column < 0 || column >= columnCount() )
554  {
555  //column out of bounds
556  return QModelIndex();
557  }
558 
559  if ( !parent.isValid() && row >= 0 && row < mData.size() )
560  {
561  //return an index for the item at this position
562  return createIndex( row, column );
563  }
564 
565  //only top level supported
566  return QModelIndex();
567 }
568 
569 QModelIndex QgsPalettedRendererModel::parent( const QModelIndex &index ) const
570 {
571  Q_UNUSED( index )
572 
573  //all items are top level
574  return QModelIndex();
575 }
576 
577 int QgsPalettedRendererModel::columnCount( const QModelIndex &parent ) const
578 {
579  if ( parent.isValid() )
580  return 0;
581 
582  return 3;
583 }
584 
585 int QgsPalettedRendererModel::rowCount( const QModelIndex &parent ) const
586 {
587  if ( parent.isValid() )
588  return 0;
589 
590  return mData.count();
591 }
592 
593 QVariant QgsPalettedRendererModel::data( const QModelIndex &index, int role ) const
594 {
595  if ( !index.isValid() )
596  return QVariant();
597 
598  switch ( role )
599  {
600  case Qt::DisplayRole:
601  case Qt::EditRole:
602  {
603  switch ( index.column() )
604  {
605  case ValueColumn:
606  return mData.at( index.row() ).value;
607 
608  case ColorColumn:
609  return mData.at( index.row() ).color;
610 
611  case LabelColumn:
612  return mData.at( index.row() ).label;
613  }
614  }
615 
616  default:
617  break;
618  }
619 
620  return QVariant();
621 }
622 
623 QVariant QgsPalettedRendererModel::headerData( int section, Qt::Orientation orientation, int role ) const
624 {
625  switch ( orientation )
626  {
627  case Qt::Vertical:
628  return QVariant();
629 
630  case Qt::Horizontal:
631  {
632  switch ( role )
633  {
634  case Qt::DisplayRole:
635  {
636  switch ( section )
637  {
638  case ValueColumn:
639  return tr( "Value" );
640 
641  case ColorColumn:
642  return tr( "Color" );
643 
644  case LabelColumn:
645  return tr( "Label" );
646  }
647  }
648 
649  }
650  break;
651  }
652 
653  default:
654  return QAbstractItemModel::headerData( section, orientation, role );
655  }
656  return QAbstractItemModel::headerData( section, orientation, role );
657 }
658 
659 bool QgsPalettedRendererModel::setData( const QModelIndex &index, const QVariant &value, int )
660 {
661  if ( !index.isValid() )
662  return false;
663  if ( index.row() >= mData.length() )
664  return false;
665 
666  switch ( index.column() )
667  {
668  case ValueColumn:
669  {
670  bool ok = false;
671  double newValue = value.toDouble( &ok );
672  if ( !ok )
673  return false;
674 
675  mData[ index.row() ].value = newValue;
676  emit dataChanged( index, index );
677  emit classesChanged();
678  return true;
679  }
680 
681  case ColorColumn:
682  {
683  mData[ index.row() ].color = value.value<QColor>();
684  emit dataChanged( index, index );
685  emit classesChanged();
686  return true;
687  }
688 
689  case LabelColumn:
690  {
691  mData[ index.row() ].label = value.toString();
692  emit dataChanged( index, index );
693  emit classesChanged();
694  return true;
695  }
696  }
697 
698  return false;
699 }
700 
701 Qt::ItemFlags QgsPalettedRendererModel::flags( const QModelIndex &index ) const
702 {
703  if ( !index.isValid() )
704  return QAbstractItemModel::flags( index ) | Qt::ItemIsDropEnabled;
705 
706  Qt::ItemFlags f = QAbstractItemModel::flags( index );
707  switch ( index.column() )
708  {
709  case ValueColumn:
710  case LabelColumn:
711  case ColorColumn:
712  f = f | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
713  break;
714  }
715  return f | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
716 }
717 
718 bool QgsPalettedRendererModel::removeRows( int row, int count, const QModelIndex &parent )
719 {
720  if ( row < 0 || row >= mData.count() )
721  return false;
722  if ( parent.isValid() )
723  return false;
724 
725  for ( int i = row + count - 1; i >= row; --i )
726  {
727  beginRemoveRows( parent, i, i );
728  mData.removeAt( i );
729  endRemoveRows();
730  }
731  emit classesChanged();
732  return true;
733 }
734 
735 bool QgsPalettedRendererModel::insertRows( int row, int count, const QModelIndex & )
736 {
737  QgsPalettedRasterRenderer::ClassData::const_iterator cIt = mData.constBegin();
738  int currentMaxValue = -std::numeric_limits<int>::max();
739  for ( ; cIt != mData.constEnd(); ++cIt )
740  {
741  int value = cIt->value;
742  currentMaxValue = std::max( value, currentMaxValue );
743  }
744  int nextValue = std::max( 0, currentMaxValue + 1 );
745 
746  beginInsertRows( QModelIndex(), row, row + count - 1 );
747  for ( int i = row; i < row + count; ++i, ++nextValue )
748  {
749  mData.insert( i, QgsPalettedRasterRenderer::Class( nextValue, QColor( 200, 200, 200 ), QLocale().toString( nextValue ) ) );
750  }
751  endInsertRows();
752  emit classesChanged();
753  return true;
754 }
755 
756 Qt::DropActions QgsPalettedRendererModel::supportedDropActions() const
757 {
758  return Qt::MoveAction;
759 }
760 
761 QStringList QgsPalettedRendererModel::mimeTypes() const
762 {
763  QStringList types;
764  types << QStringLiteral( "application/x-qgspalettedrenderermodel" );
765  return types;
766 }
767 
768 QMimeData *QgsPalettedRendererModel::mimeData( const QModelIndexList &indexes ) const
769 {
770  QMimeData *mimeData = new QMimeData();
771  QByteArray encodedData;
772 
773  QDataStream stream( &encodedData, QIODevice::WriteOnly );
774 
775  // Create list of rows
776  const auto constIndexes = indexes;
777  for ( const QModelIndex &index : constIndexes )
778  {
779  if ( !index.isValid() || index.column() != 0 )
780  continue;
781 
782  stream << index.row();
783  }
784  mimeData->setData( QStringLiteral( "application/x-qgspalettedrenderermodel" ), encodedData );
785  return mimeData;
786 }
787 
788 bool QgsPalettedRendererModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex & )
789 {
790  Q_UNUSED( column )
791  if ( action != Qt::MoveAction ) return true;
792 
793  if ( !data->hasFormat( QStringLiteral( "application/x-qgspalettedrenderermodel" ) ) )
794  return false;
795 
796  QByteArray encodedData = data->data( QStringLiteral( "application/x-qgspalettedrenderermodel" ) );
797  QDataStream stream( &encodedData, QIODevice::ReadOnly );
798 
799  QVector<int> rows;
800  while ( !stream.atEnd() )
801  {
802  int r;
803  stream >> r;
804  rows.append( r );
805  }
806 
808  for ( int i = 0; i < rows.count(); ++i )
809  newData << mData.at( rows.at( i ) );
810 
811  if ( row < 0 )
812  row = mData.count();
813 
814  beginInsertRows( QModelIndex(), row, row + rows.count() - 1 );
815  for ( int i = 0; i < rows.count(); ++i )
816  mData.insert( row + i, newData.at( i ) );
817  endInsertRows();
818  emit classesChanged();
819  return true;
820 }
821 
822 QModelIndex QgsPalettedRendererModel::addEntry( const QColor &color )
823 {
824  insertRow( rowCount() );
825  QModelIndex newRow = index( mData.count() - 1, 1 );
826  setData( newRow, color );
827  return newRow;
828 }
829 
830 void QgsPalettedRendererModel::deleteAll()
831 {
832  beginResetModel();
833  mData.clear();
834  endResetModel();
835  emit classesChanged();
836 }
837 
838 //
839 // QgsPalettedRendererClassGatherer
840 //
841 
842 QgsPalettedRendererClassGatherer::QgsPalettedRendererClassGatherer( QgsRasterLayer *layer, int bandNumber, const QgsPalettedRasterRenderer::ClassData &existingClasses, QgsColorRamp *ramp )
843  : mLayer( layer )
844  , mBandNumber( bandNumber )
845  , mRamp( ramp )
846  , mClasses( existingClasses )
847  , mWasCanceled( false )
848 {}
849 
850 void QgsPalettedRendererClassGatherer::run()
851 {
852  mWasCanceled = false;
853 
854  // allow responsive cancellation
855  mFeedback = new QgsRasterBlockFeedback();
856  connect( mFeedback, &QgsRasterBlockFeedback::progressChanged, this, &QgsPalettedRendererClassGatherer::progressChanged );
857 
858  QgsPalettedRasterRenderer::ClassData newClasses = QgsPalettedRasterRenderer::classDataFromRaster( mLayer->dataProvider(), mBandNumber, mRamp.get(), mFeedback );
859 
860  // combine existing classes with new classes
861  QgsPalettedRasterRenderer::ClassData::iterator classIt = newClasses.begin();
862  emit progressChanged( 0 );
863  qlonglong i = 0;
864  for ( ; classIt != newClasses.end(); ++classIt )
865  {
866  // check if existing classes contains this same class
867  for ( const QgsPalettedRasterRenderer::Class &existingClass : std::as_const( mClasses ) )
868  {
869  if ( existingClass.value == classIt->value )
870  {
871  classIt->color = existingClass.color;
872  classIt->label = existingClass.label;
873  break;
874  }
875  }
876  i ++;
877  emit progressChanged( 100 * ( i / static_cast<float>( newClasses.count() ) ) );
878  }
879  mClasses = newClasses;
880 
881  // be overly cautious - it's *possible* stop() might be called between deleting mFeedback and nulling it
882  mFeedbackMutex.lock();
883  delete mFeedback;
884  mFeedback = nullptr;
885  mFeedbackMutex.unlock();
886 
887  emit collectedClasses();
888 }
889 
890 
891 QgsPalettedRasterRenderer::ClassData QgsPalettedRendererProxyModel::classData() const
892 {
894  for ( int i = 0; i < rowCount( ); ++i )
895  {
896  data.push_back( qobject_cast<QgsPalettedRendererModel *>( sourceModel() )->classAtIndex( mapToSource( index( i, 0 ) ) ) );
897  }
898  return data;
899 }
900 
901 
QgsCompoundColorWidget::setAllowOpacity
void setAllowOpacity(bool allowOpacity)
Sets whether opacity modification (transparency) is permitted for the color dialog.
Definition: qgscompoundcolorwidget.cpp:303
QgsProject::layerWillBeRemoved
void layerWillBeRemoved(const QString &layerId)
Emitted when a layer is about to be removed from the registry.
QgsPalettedRasterRenderer::ClassData
QList< QgsPalettedRasterRenderer::Class > ClassData
Map of value to class properties.
Definition: qgspalettedrasterrenderer.h:59
QgsRasterBandComboBox::bandChanged
void bandChanged(int band)
Emitted when the currently selected band changes.
QgsColorRamp
Abstract base class for color ramps.
Definition: qgscolorramp.h:29
qgscolorrampimpl.h
qgspalettedrendererwidget.h
qgsrasterlayer.h
QgsPalettedRasterRenderer::sourceColorRamp
QgsColorRamp * sourceColorRamp() const
Gets the source color ramp.
Definition: qgspalettedrasterrenderer.cpp:372
QgsRasterDataProvider::dataType
Qgis::DataType dataType(int bandNo) const override=0
Returns data type for the band specified by number.
QgsSettings::value
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
Definition: qgssettings.cpp:161
QgsRasterRendererWidget::mRasterLayer
QgsRasterLayer * mRasterLayer
Definition: qgsrasterrendererwidget.h:123
QgsColorSwatchDelegate
A delegate for showing a color swatch in a list.
Definition: qgscolorschemelist.h:36
QgsPanelWidget::findParentPanel
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
Definition: qgspanelwidget.cpp:54
QgsPalettedRendererWidget::QgsPalettedRendererWidget
QgsPalettedRendererWidget(QgsRasterLayer *layer, const QgsRectangle &extent=QgsRectangle())
Definition: qgspalettedrendererwidget.cpp:42
qgslocaleawarenumericlineeditdelegate.h
QgsProject::instance
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:480
QgsPalettedRendererWidget::renderer
QgsRasterRenderer * renderer() override
Creates a new renderer, using the properties defined in the widget.
Definition: qgspalettedrendererwidget.cpp:141
QgsSettings
This class is a composition of two QSettings instances:
Definition: qgssettings.h:61
QgsPalettedRendererWidget::setFromRenderer
void setFromRenderer(const QgsRasterRenderer *r)
Sets the widget state from the specified renderer.
Definition: qgspalettedrendererwidget.cpp:154
QgsPalettedRasterRenderer::colorTableToClassData
static QgsPalettedRasterRenderer::ClassData colorTableToClassData(const QList< QgsColorRampShader::ColorRampItem > &table)
Converts a raster color table to paletted renderer class data.
Definition: qgspalettedrasterrenderer.cpp:377
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QgsProject
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:103
QgsRandomColorRamp
Totally random color ramp. Returns colors generated at random, but constrained to some hardcoded satu...
Definition: qgscolorrampimpl.h:492
QgsCompoundColorWidget
A custom QGIS widget for selecting a color, including options for selecting colors via hue wheel,...
Definition: qgscompoundcolorwidget.h:33
QgsPalettedRasterRenderer::setSourceColorRamp
void setSourceColorRamp(QgsColorRamp *ramp)
Set the source color ramp.
Definition: qgspalettedrasterrenderer.cpp:367
QgsRasterLayer::renderer
QgsRasterRenderer * renderer() const
Returns the raster's renderer.
Definition: qgsrasterlayer.cpp:1758
QgsCompoundColorWidget::LayoutVertical
@ LayoutVertical
Use a narrower, vertically stacked layout.
Definition: qgscompoundcolorwidget.h:44
QgsCompoundColorWidget::currentColorChanged
void currentColorChanged(const QColor &color)
Emitted when the dialog's color changes.
QgsPanelWidget
Base class for any widget that can be shown as a inline panel.
Definition: qgspanelwidget.h:29
whileBlocking
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:2191
qgspalettedrasterrenderer.h
QgsRasterRenderer
Raster renderer pipe that applies colors to a raster.
Definition: qgsrasterrenderer.h:40
QgsPalettedRasterRenderer::classDataFromRaster
static QgsPalettedRasterRenderer::ClassData classDataFromRaster(QgsRasterInterface *raster, int bandNumber, QgsColorRamp *ramp=nullptr, QgsRasterBlockFeedback *feedback=nullptr)
Generates class data from a raster, for the specified bandNumber.
Definition: qgspalettedrasterrenderer.cpp:507
Qgis::DataType::UnknownDataType
@ UnknownDataType
Unknown or unspecified type.
QgsPalettedRasterRenderer::band
int band() const
Returns the raster band used for rendering the raster.
Definition: qgspalettedrasterrenderer.h:100
QgsPalettedRasterRenderer::Class
Properties of a single value class.
Definition: qgspalettedrasterrenderer.h:40
Qgis::UI_SCALE_FACTOR
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:2043
QgsFeedback::progressChanged
void progressChanged(double progress)
Emitted when the feedback object reports a progress change.
QgsPalettedRasterRenderer::classDataToString
static QString classDataToString(const QgsPalettedRasterRenderer::ClassData &classes)
Converts classes to a string representation, using the .clr/gdal color table file format.
Definition: qgspalettedrasterrenderer.cpp:488
QgsPalettedRendererWidget::~QgsPalettedRendererWidget
~QgsPalettedRendererWidget() override
Definition: qgspalettedrendererwidget.cpp:132
QgsSettings::setValue
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Definition: qgssettings.cpp:279
QgsRasterDataProvider::colorTable
virtual QList< QgsColorRampShader::ColorRampItem > colorTable(int bandNo) const
Definition: qgsrasterdataprovider.h:257
QgsPalettedRasterRenderer
Renderer for paletted raster images.
Definition: qgspalettedrasterrenderer.h:35
QgsRasterLayer
Represents a raster layer.
Definition: qgsrasterlayer.h:76
QgsPanelWidget::setPanelTitle
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
Definition: qgspanelwidget.h:44
qgscolordialog.h
QgsMapLayer
Base class for all map layer types. This is the base class for all map layer types (vector,...
Definition: qgsmaplayer.h:72
QgsRasterBlockFeedback
Feedback object tailored for raster block reading.
Definition: qgsrasterinterface.h:41
qgssettings.h
QgsPalettedRasterRenderer::classes
ClassData classes() const
Returns a map of value to classes (colors) used by the renderer.
Definition: qgspalettedrasterrenderer.cpp:103
QgsRasterRendererWidget
Abstract base class for widgets which configure a QgsRasterRenderer.
Definition: qgsrasterrendererwidget.h:39
QgsRasterRendererWidget::widgetChanged
void widgetChanged()
Emitted when something on the widget has changed.
qgscolorrampshaderwidget.h
QgsPalettedRasterRenderer::classDataFromFile
static QgsPalettedRasterRenderer::ClassData classDataFromFile(const QString &path)
Opens a color table file and returns corresponding paletted renderer class data.
Definition: qgspalettedrasterrenderer.cpp:475
QgsRasterDataProvider
Base class for raster data providers.
Definition: qgsrasterdataprovider.h:88
qgsproject.h
QgsColorDialog::getColor
static QColor getColor(const QColor &initialColor, QWidget *parent, const QString &title=QString(), bool allowOpacity=false)
Returns a color selection from a color dialog.
Definition: qgscolordialog.cpp:81
QgsRasterLayer::dataProvider
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
Definition: qgsrasterlayer.cpp:257
qgsrasterdataprovider.h
QgsColorRampButton::colorRampChanged
void colorRampChanged()
Emitted whenever a new color ramp is set for the button.