QGIS API Documentation  3.27.0-Master (597e8eebd4)
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 
@ UnknownDataType
Unknown or unspecified type.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:2039
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.
Abstract base class for color ramps.
Definition: qgscolorramp.h:30
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:73
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
Creates a new renderer, using the properties defined in the widget.
void setFromRenderer(const QgsRasterRenderer *r)
Sets the widget state from the specified renderer.
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:104
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:479
void layerWillBeRemoved(const QString &layerId)
Emitted when a layer is about to be removed from the registry.
Totally random color ramp.
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.
QgsRasterRenderer * renderer() const
Returns the raster's renderer.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
Abstract base class for widgets which configure a QgsRasterRenderer.
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:2186
Properties of a single value class.