QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
qgsprocessingmultipleselectiondialog.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsprocessingmultipleselectiondialog.cpp
3  ------------------------------------
4  Date : February 2019
5  Copyright : (C) 2019 Nyall Dawson
6  Email : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
17 #include "qgsgui.h"
18 #include "qgssettings.h"
19 #include "qgsfileutils.h"
20 #include "qgsvectorlayer.h"
21 #include "qgsmeshlayer.h"
22 #include "qgsrasterlayer.h"
23 #include "processing/models/qgsprocessingmodelchildparametersource.h"
24 #include <QStandardItemModel>
25 #include <QStandardItem>
26 #include <QPushButton>
27 #include <QLineEdit>
28 #include <QToolButton>
29 #include <QFileDialog>
30 
32 
33 QgsProcessingMultipleSelectionPanelWidget::QgsProcessingMultipleSelectionPanelWidget( const QVariantList &availableOptions,
34  const QVariantList &selectedOptions,
35  QWidget *parent )
36  : QgsPanelWidget( parent )
37  , mValueFormatter( []( const QVariant & v )->QString
38 {
39  if ( v.canConvert< QgsProcessingModelChildParameterSource >() )
40  return v.value< QgsProcessingModelChildParameterSource >().staticValue().toString();
41  else
42  return v.toString();
43 } )
44 {
45  setupUi( this );
46 
48 
49  mSelectionList->setSelectionBehavior( QAbstractItemView::SelectRows );
50  mSelectionList->setSelectionMode( QAbstractItemView::ExtendedSelection );
51  mSelectionList->setDragDropMode( QAbstractItemView::InternalMove );
52 
53  mButtonSelectAll = new QPushButton( tr( "Select All" ) );
54  mButtonBox->addButton( mButtonSelectAll, QDialogButtonBox::ActionRole );
55 
56  mButtonClearSelection = new QPushButton( tr( "Clear Selection" ) );
57  mButtonBox->addButton( mButtonClearSelection, QDialogButtonBox::ActionRole );
58 
59  mButtonToggleSelection = new QPushButton( tr( "Toggle Selection" ) );
60  mButtonBox->addButton( mButtonToggleSelection, QDialogButtonBox::ActionRole );
61 
62  connect( mButtonSelectAll, &QPushButton::clicked, this, [ = ] { selectAll( true ); } );
63  connect( mButtonClearSelection, &QPushButton::clicked, this, [ = ] { selectAll( false ); } );
64  connect( mButtonToggleSelection, &QPushButton::clicked, this, &QgsProcessingMultipleSelectionPanelWidget::toggleSelection );
65 
66  connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsProcessingMultipleSelectionPanelWidget::acceptClicked );
67  populateList( availableOptions, selectedOptions );
68 
69  connect( mModel, &QStandardItemModel::itemChanged, this, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged );
70 }
71 
72 void QgsProcessingMultipleSelectionPanelWidget::setValueFormatter( const std::function<QString( const QVariant & )> &formatter )
73 {
74  mValueFormatter = formatter;
75  // update item text using new formatter
76  for ( int i = 0; i < mModel->rowCount(); ++i )
77  {
78  mModel->item( i )->setText( mValueFormatter( mModel->item( i )->data( Qt::UserRole ) ) );
79  }
80 }
81 
82 QVariantList QgsProcessingMultipleSelectionPanelWidget::selectedOptions() const
83 {
84  QVariantList options;
85  options.reserve( mModel->rowCount() );
86  bool hasModelSources = false;
87  for ( int i = 0; i < mModel->rowCount(); ++i )
88  {
89  if ( mModel->item( i )->checkState() == Qt::Checked )
90  {
91  const QVariant option = mModel->item( i )->data( Qt::UserRole );
92 
93  if ( option.canConvert< QgsProcessingModelChildParameterSource >() )
94  hasModelSources = true;
95 
96  options << option;
97  }
98  }
99 
100  if ( hasModelSources )
101  {
102  // if any selected value is a QgsProcessingModelChildParameterSource, then we need to upgrade them all
103  QVariantList originalOptions = options;
104  options.clear();
105  for ( const QVariant &option : originalOptions )
106  {
107  if ( option.canConvert< QgsProcessingModelChildParameterSource >() )
108  options << option;
109  else
110  options << QVariant::fromValue( QgsProcessingModelChildParameterSource::fromStaticValue( option ) );
111  }
112  }
113 
114  return options;
115 }
116 
117 
118 void QgsProcessingMultipleSelectionPanelWidget::selectAll( const bool checked )
119 {
120  const QList<QStandardItem *> items = currentItems();
121  for ( QStandardItem *item : items )
122  {
123  item->setCheckState( checked ? Qt::Checked : Qt::Unchecked );
124  }
125 }
126 
127 void QgsProcessingMultipleSelectionPanelWidget::toggleSelection()
128 {
129  const QList<QStandardItem *> items = currentItems();
130  for ( QStandardItem *item : items )
131  {
132  item->setCheckState( item->checkState() == Qt::Unchecked ? Qt::Checked : Qt::Unchecked );
133  }
134 }
135 
136 QList<QStandardItem *> QgsProcessingMultipleSelectionPanelWidget::currentItems()
137 {
138  QList<QStandardItem *> items;
139  const QModelIndexList selection = mSelectionList->selectionModel()->selectedIndexes();
140  if ( selection.size() > 1 )
141  {
142  items.reserve( selection.size() );
143  for ( const QModelIndex &index : selection )
144  {
145  items << mModel->itemFromIndex( index );
146  }
147  }
148  else
149  {
150  items.reserve( mModel->rowCount() );
151  for ( int i = 0; i < mModel->rowCount(); ++i )
152  {
153  items << mModel->item( i );
154  }
155  }
156  return items;
157 }
158 
159 void QgsProcessingMultipleSelectionPanelWidget::populateList( const QVariantList &availableOptions, const QVariantList &selectedOptions )
160 {
161  mModel = new QStandardItemModel( this );
162 
163  QVariantList remainingOptions = availableOptions;
164 
165  // we add selected options first, keeping the existing order of options
166  for ( const QVariant &option : selectedOptions )
167  {
168 // if isinstance(t, QgsProcessingModelChildParameterSource):
169 // item = QStandardItem(t.staticValue())
170  // else:
171 
172  addOption( option, mValueFormatter( option ), true );
173  remainingOptions.removeAll( option );
174  }
175 
176  for ( const QVariant &option : qgis::as_const( remainingOptions ) )
177  {
178  addOption( option, mValueFormatter( option ), false );
179  }
180 
181  mSelectionList->setModel( mModel );
182 }
183 
184 
185 void QgsProcessingMultipleSelectionPanelWidget::addOption( const QVariant &value, const QString &title, bool selected, bool updateExistingTitle )
186 {
187  // don't add duplicate options
188  for ( int i = 0; i < mModel->rowCount(); ++i )
189  {
190  if ( mModel->item( i )->data( Qt::UserRole ) == value ||
191  ( mModel->item( i )->data( Qt::UserRole ).canConvert< QgsProcessingModelChildParameterSource >() &&
192  value.canConvert< QgsProcessingModelChildParameterSource >() &&
193  mModel->item( i )->data( Qt::UserRole ).value< QgsProcessingModelChildParameterSource >() ==
194  value.value< QgsProcessingModelChildParameterSource >() )
195  )
196  {
197  if ( updateExistingTitle )
198  mModel->item( i )->setText( title );
199  return;
200  }
201  }
202 
203  std::unique_ptr< QStandardItem > item = qgis::make_unique< QStandardItem >( title );
204  item->setData( value, Qt::UserRole );
205  item->setCheckState( selected ? Qt::Checked : Qt::Unchecked );
206  item->setCheckable( true );
207  item->setDropEnabled( false );
208  mModel->appendRow( item.release() );
209 }
210 
211 //
212 // QgsProcessingMultipleSelectionDialog
213 //
214 
215 
216 
217 QgsProcessingMultipleSelectionDialog::QgsProcessingMultipleSelectionDialog( const QVariantList &availableOptions, const QVariantList &selectedOptions, QWidget *parent, Qt::WindowFlags flags )
218  : QDialog( parent, flags )
219 {
220  setWindowTitle( tr( "Multiple Selection" ) );
221  QVBoxLayout *vLayout = new QVBoxLayout();
222  mWidget = new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
223  vLayout->addWidget( mWidget );
224  mWidget->buttonBox()->addButton( QDialogButtonBox::Cancel );
225  connect( mWidget->buttonBox(), &QDialogButtonBox::accepted, this, &QDialog::accept );
226  connect( mWidget->buttonBox(), &QDialogButtonBox::rejected, this, &QDialog::reject );
227  setLayout( vLayout );
228 }
229 
230 void QgsProcessingMultipleSelectionDialog::setValueFormatter( const std::function<QString( const QVariant & )> &formatter )
231 {
232  mWidget->setValueFormatter( formatter );
233 }
234 
235 QVariantList QgsProcessingMultipleSelectionDialog::selectedOptions() const
236 {
237  return mWidget->selectedOptions();
238 }
239 
240 
241 //
242 // QgsProcessingMultipleInputPanelWidget
243 //
244 
245 QgsProcessingMultipleInputPanelWidget::QgsProcessingMultipleInputPanelWidget( const QgsProcessingParameterMultipleLayers *parameter, const QVariantList &selectedOptions,
246  const QList<QgsProcessingModelChildParameterSource> &modelSources,
247  QgsProcessingModelAlgorithm *model, QWidget *parent )
248  : QgsProcessingMultipleSelectionPanelWidget( QVariantList(), selectedOptions, parent )
249  , mParameter( parameter )
250 {
251  QPushButton *addFileButton = new QPushButton( tr( "Add File(s)…" ) );
252  connect( addFileButton, &QPushButton::clicked, this, &QgsProcessingMultipleInputPanelWidget::addFiles );
253  buttonBox()->addButton( addFileButton, QDialogButtonBox::ActionRole );
254 
255  QPushButton *addDirButton = new QPushButton( tr( "Add Directory…" ) );
256  connect( addDirButton, &QPushButton::clicked, this, &QgsProcessingMultipleInputPanelWidget::addDirectory );
257  buttonBox()->addButton( addDirButton, QDialogButtonBox::ActionRole );
258 
259  for ( const QgsProcessingModelChildParameterSource &source : modelSources )
260  {
261  addOption( QVariant::fromValue( source ), source.friendlyIdentifier( model ), false, true );
262  }
263 }
264 
265 void QgsProcessingMultipleInputPanelWidget::setProject( QgsProject *project )
266 {
267  if ( mParameter->layerType() != QgsProcessing::TypeFile )
268  populateFromProject( project );
269 }
270 
271 void QgsProcessingMultipleInputPanelWidget::addFiles()
272 {
273  QgsSettings settings;
274  QString path = settings.value( QStringLiteral( "/Processing/LastInputPath" ), QDir::homePath() ).toString();
275 
276  QString filter;
277  if ( const QgsFileFilterGenerator *generator = dynamic_cast< const QgsFileFilterGenerator * >( mParameter ) )
278  filter = generator->createFileFilter();
279  else
280  filter = QObject::tr( "All files (*.*)" );
281 
282  const QStringList filenames = QFileDialog::getOpenFileNames( this, tr( "Select File(s)" ), path, filter );
283  if ( filenames.empty() )
284  return;
285 
286  settings.setValue( QStringLiteral( "/Processing/LastInputPath" ), QFileInfo( filenames.at( 0 ) ).path() );
287 
288  for ( const QString &file : filenames )
289  {
290  addOption( file, file, true );
291  }
292 
293  emit selectionChanged();
294 }
295 
296 void QgsProcessingMultipleInputPanelWidget::addDirectory()
297 {
298  QgsSettings settings;
299  QString path = settings.value( QStringLiteral( "/Processing/LastInputPath" ), QDir::homePath() ).toString();
300 
301  const QString dir = QFileDialog::getExistingDirectory( this, tr( "Select Directory" ), path );
302  if ( dir.isEmpty() )
303  return;
304 
305  settings.setValue( QStringLiteral( "/Processing/LastInputPath" ), dir );
306 
307  QStringList nameFilters;
308  if ( const QgsFileFilterGenerator *generator = dynamic_cast< const QgsFileFilterGenerator * >( mParameter ) )
309  {
310  const QStringList extensions = QgsFileUtils::extensionsFromFilter( generator->createFileFilter() );
311  for ( const QString &extension : extensions )
312  {
313  nameFilters << QStringLiteral( "*.%1" ).arg( extension );
314  nameFilters << QStringLiteral( "*.%1" ).arg( extension.toUpper() );
315  nameFilters << QStringLiteral( "*.%1" ).arg( extension.toLower() );
316  }
317  }
318 
319  QDirIterator it( path, nameFilters, QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot, QDirIterator::Subdirectories );
320  QStringList files;
321  while ( it.hasNext() )
322  {
323  const QString fullPath = it.next();
324  addOption( fullPath, fullPath, true );
325  }
326  emit selectionChanged();
327 }
328 
329 void QgsProcessingMultipleInputPanelWidget::populateFromProject( QgsProject *project )
330 {
331  QgsSettings settings;
332  auto addLayer = [&]( const QgsMapLayer * layer )
333  {
334  const QString authid = layer->crs().authid();
335  QString title;
336  if ( settings.value( QStringLiteral( "Processing/Configuration/SHOW_CRS_DEF" ), true ).toBool() && !authid.isEmpty() )
337  title = QStringLiteral( "%1 [%2]" ).arg( layer->name(), authid );
338  else
339  title = layer->name();
340 
341 
342  QString id = layer->id();
343  for ( int i = 0; i < mModel->rowCount(); ++i )
344  {
345  // try to match project layers to current layers
346  if ( mModel->item( i )->data( Qt::UserRole ) == layer->id() )
347  {
348  id = layer->id();
349  break;
350  }
351  else if ( mModel->item( i )->data( Qt::UserRole ) == layer->source() )
352  {
353  id = layer->source();
354  break;
355  }
356  }
357 
358  addOption( id, title, false, true );
359  };
360 
361  switch ( mParameter->layerType() )
362  {
364  break;
365 
367  {
368  const QList<QgsRasterLayer *> options = QgsProcessingUtils::compatibleRasterLayers( project, false );
369  for ( const QgsRasterLayer *layer : options )
370  {
371  addLayer( layer );
372  }
373  break;
374  }
375 
377  {
378  const QList<QgsMeshLayer *> options = QgsProcessingUtils::compatibleMeshLayers( project, false );
379  for ( const QgsMeshLayer *layer : options )
380  {
381  addLayer( layer );
382  }
383 
384  break;
385  }
386 
389  {
390  const QList<QgsVectorLayer *> options = QgsProcessingUtils::compatibleVectorLayers( project, QList< int >() );
391  for ( const QgsVectorLayer *layer : options )
392  {
393  addLayer( layer );
394  }
395 
396  break;
397  }
398 
400  {
401  const QList<QgsVectorLayer *> vectors = QgsProcessingUtils::compatibleVectorLayers( project, QList< int >() );
402  for ( const QgsVectorLayer *layer : vectors )
403  {
404  addLayer( layer );
405  }
406  const QList<QgsRasterLayer *> rasters = QgsProcessingUtils::compatibleRasterLayers( project );
407  for ( const QgsRasterLayer *layer : rasters )
408  {
409  addLayer( layer );
410  }
411  const QList<QgsMeshLayer *> meshes = QgsProcessingUtils::compatibleMeshLayers( project );
412  for ( const QgsMeshLayer *layer : meshes )
413  {
414  addLayer( layer );
415  }
416 
417  break;
418  }
419 
423  {
424  const QList<QgsVectorLayer *> vectors = QgsProcessingUtils::compatibleVectorLayers( project, QList< int >() << mParameter->layerType() );
425  for ( const QgsVectorLayer *layer : vectors )
426  {
427  addLayer( layer );
428  }
429  break;
430  }
431  }
432 }
433 
434 //
435 // QgsProcessingMultipleInputDialog
436 //
437 
438 QgsProcessingMultipleInputDialog::QgsProcessingMultipleInputDialog( const QgsProcessingParameterMultipleLayers *parameter, const QVariantList &selectedOptions,
439  const QList< QgsProcessingModelChildParameterSource > &modelSources, QgsProcessingModelAlgorithm *model, QWidget *parent, Qt::WindowFlags flags )
440  : QDialog( parent, flags )
441 {
442  setWindowTitle( tr( "Multiple Selection" ) );
443  QVBoxLayout *vLayout = new QVBoxLayout();
444  mWidget = new QgsProcessingMultipleInputPanelWidget( parameter, selectedOptions, modelSources, model );
445  vLayout->addWidget( mWidget );
446  mWidget->buttonBox()->addButton( QDialogButtonBox::Cancel );
447  connect( mWidget->buttonBox(), &QDialogButtonBox::accepted, this, &QDialog::accept );
448  connect( mWidget->buttonBox(), &QDialogButtonBox::rejected, this, &QDialog::reject );
449  setLayout( vLayout );
450 }
451 
452 QVariantList QgsProcessingMultipleInputDialog::selectedOptions() const
453 {
454  return mWidget->selectedOptions();
455 }
456 
457 void QgsProcessingMultipleInputDialog::setProject( QgsProject *project )
458 {
459  mWidget->setProject( project );
460 }
461 
462 
formatter
Definition: qgsbasicnumericformat.cpp:25
QgsProcessingUtils::compatibleMeshLayers
static QList< QgsMeshLayer * > compatibleMeshLayers(QgsProject *project, bool sort=true)
Returns a list of mesh layers from a project which are compatible with the processing framework.
Definition: qgsprocessingutils.cpp:85
QgsFileUtils::extensionsFromFilter
static QStringList extensionsFromFilter(const QString &filter)
Returns a list of the extensions contained within a file filter string.
Definition: qgsfileutils.cpp:40
qgsrasterlayer.h
QgsProcessingUtils::compatibleRasterLayers
static QList< QgsRasterLayer * > compatibleRasterLayers(QgsProject *project, bool sort=true)
Returns a list of raster layers from a project which are compatible with the processing framework.
Definition: qgsprocessingutils.cpp:38
QgsSettings::value
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
Definition: qgssettings.cpp:174
qgsgui.h
QgsProcessing::TypeVectorPolygon
@ TypeVectorPolygon
Vector polygon layers.
Definition: qgsprocessing.h:50
QgsProcessing::TypeVectorLine
@ TypeVectorLine
Vector line layers.
Definition: qgsprocessing.h:49
QgsSettings
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QgsProcessing::TypeVectorPoint
@ TypeVectorPoint
Vector point layers.
Definition: qgsprocessing.h:48
QgsProcessingParameterMultipleLayers
A parameter for processing algorithms which accepts multiple map layers.
Definition: qgsprocessingparameters.h:1878
QgsProject
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:95
QgsGui::enableAutoGeometryRestore
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition: qgsgui.cpp:139
QgsProcessing::TypeMapLayer
@ TypeMapLayer
Any map layer type (raster or vector or mesh)
Definition: qgsprocessing.h:46
QgsPanelWidget
Base class for any widget that can be shown as a inline panel.
Definition: qgspanelwidget.h:30
QgsProcessing::TypeVector
@ TypeVector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:53
QgsFileFilterGenerator
Abstract interface for classes which generate a file filter string.
Definition: qgsfilefiltergenerator.h:34
QgsProcessing::TypeMesh
@ TypeMesh
Mesh layers.
Definition: qgsprocessing.h:54
QgsMeshLayer
Represents a mesh layer supporting display of data on structured or unstructured meshes.
Definition: qgsmeshlayer.h:95
QgsProcessing::TypeRaster
@ TypeRaster
Raster layers.
Definition: qgsprocessing.h:51
QgsProcessing::TypeVectorAnyGeometry
@ TypeVectorAnyGeometry
Any vector layer with geometry.
Definition: qgsprocessing.h:47
QgsSettings::setValue
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Definition: qgssettings.cpp:289
QgsRasterLayer
Represents a raster layer.
Definition: qgsrasterlayer.h:71
qgsfileutils.h
qgsmeshlayer.h
qgsvectorlayer.h
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:387
QgsProcessingUtils::compatibleVectorLayers
static QList< QgsVectorLayer * > compatibleVectorLayers(QgsProject *project, const QList< int > &sourceTypes=QList< int >(), bool sort=true)
Returns a list of vector layers from a project which are compatible with the processing framework.
Definition: qgsprocessingutils.cpp:62
QgsMapLayer
Base class for all map layer types.
Definition: qgsmaplayer.h:83
qgssettings.h
QgsProcessing::TypeFile
@ TypeFile
Files (i.e. non map layer sources, such as text files)
Definition: qgsprocessing.h:52
qgsprocessingmultipleselectiondialog.h