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