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