QGIS API Documentation  3.21.0-Master (5b68dc587e)
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 "qgspluginlayer.h"
24 #include "qgspointcloudlayer.h"
25 #include "qgsannotationlayer.h"
26 #include "qgsproject.h"
27 #include "processing/models/qgsprocessingmodelchildparametersource.h"
28 #include <QStandardItemModel>
29 #include <QStandardItem>
30 #include <QPushButton>
31 #include <QLineEdit>
32 #include <QToolButton>
33 #include <QFileDialog>
34 #include <QDirIterator>
35 
37 
38 QgsProcessingMultipleSelectionPanelWidget::QgsProcessingMultipleSelectionPanelWidget( const QVariantList &availableOptions,
39  const QVariantList &selectedOptions,
40  QWidget *parent )
41  : QgsPanelWidget( parent )
42  , mValueFormatter( []( const QVariant & v )->QString
43 {
44  if ( v.canConvert< QgsProcessingModelChildParameterSource >() )
45  return v.value< QgsProcessingModelChildParameterSource >().staticValue().toString();
46  else
47  return v.toString();
48 } )
49 {
50  setupUi( this );
51 
53 
54  mSelectionList->setSelectionBehavior( QAbstractItemView::SelectRows );
55  mSelectionList->setSelectionMode( QAbstractItemView::ExtendedSelection );
56  mSelectionList->setDragDropMode( QAbstractItemView::InternalMove );
57 
58  mButtonSelectAll = new QPushButton( tr( "Select All" ) );
59  mButtonBox->addButton( mButtonSelectAll, QDialogButtonBox::ActionRole );
60 
61  mButtonClearSelection = new QPushButton( tr( "Clear Selection" ) );
62  mButtonBox->addButton( mButtonClearSelection, QDialogButtonBox::ActionRole );
63 
64  mButtonToggleSelection = new QPushButton( tr( "Toggle Selection" ) );
65  mButtonBox->addButton( mButtonToggleSelection, QDialogButtonBox::ActionRole );
66 
67  connect( mButtonSelectAll, &QPushButton::clicked, this, [ = ] { selectAll( true ); } );
68  connect( mButtonClearSelection, &QPushButton::clicked, this, [ = ] { selectAll( false ); } );
69  connect( mButtonToggleSelection, &QPushButton::clicked, this, &QgsProcessingMultipleSelectionPanelWidget::toggleSelection );
70 
71  connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsProcessingMultipleSelectionPanelWidget::acceptClicked );
72  populateList( availableOptions, selectedOptions );
73 
74  connect( mModel, &QStandardItemModel::itemChanged, this, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged );
75 
76  // When user moves an item, a new item is created and another one is removed, so we need to fire selectionChanged
77  // see https://github.com/qgis/QGIS/issues/44270
78  connect( mModel, &QStandardItemModel::rowsRemoved, this, &QgsProcessingMultipleSelectionPanelWidget::selectionChanged );
79 }
80 
81 void QgsProcessingMultipleSelectionPanelWidget::setValueFormatter( const std::function<QString( const QVariant & )> &formatter )
82 {
83  mValueFormatter = formatter;
84  // update item text using new formatter
85  for ( int i = 0; i < mModel->rowCount(); ++i )
86  {
87  mModel->item( i )->setText( mValueFormatter( mModel->item( i )->data( Qt::UserRole ) ) );
88  }
89 }
90 
91 QVariantList QgsProcessingMultipleSelectionPanelWidget::selectedOptions() const
92 {
93  QVariantList options;
94  options.reserve( mModel->rowCount() );
95  bool hasModelSources = false;
96  for ( int i = 0; i < mModel->rowCount(); ++i )
97  {
98  if ( mModel->item( i )->checkState() == Qt::Checked )
99  {
100  const QVariant option = mModel->item( i )->data( Qt::UserRole );
101 
102  if ( option.canConvert< QgsProcessingModelChildParameterSource >() )
103  hasModelSources = true;
104 
105  options << option;
106  }
107  }
108 
109  if ( hasModelSources )
110  {
111  // if any selected value is a QgsProcessingModelChildParameterSource, then we need to upgrade them all
112  QVariantList originalOptions = options;
113  options.clear();
114  for ( const QVariant &option : originalOptions )
115  {
116  if ( option.canConvert< QgsProcessingModelChildParameterSource >() )
117  options << option;
118  else
119  options << QVariant::fromValue( QgsProcessingModelChildParameterSource::fromStaticValue( option ) );
120  }
121  }
122 
123  return options;
124 }
125 
126 
127 void QgsProcessingMultipleSelectionPanelWidget::selectAll( const bool checked )
128 {
129  const QList<QStandardItem *> items = currentItems();
130  for ( QStandardItem *item : items )
131  {
132  item->setCheckState( checked ? Qt::Checked : Qt::Unchecked );
133  }
134 }
135 
136 void QgsProcessingMultipleSelectionPanelWidget::toggleSelection()
137 {
138  const QList<QStandardItem *> items = currentItems();
139  for ( QStandardItem *item : items )
140  {
141  item->setCheckState( item->checkState() == Qt::Unchecked ? Qt::Checked : Qt::Unchecked );
142  }
143 }
144 
145 QList<QStandardItem *> QgsProcessingMultipleSelectionPanelWidget::currentItems()
146 {
147  QList<QStandardItem *> items;
148  const QModelIndexList selection = mSelectionList->selectionModel()->selectedIndexes();
149  if ( selection.size() > 1 )
150  {
151  items.reserve( selection.size() );
152  for ( const QModelIndex &index : selection )
153  {
154  items << mModel->itemFromIndex( index );
155  }
156  }
157  else
158  {
159  items.reserve( mModel->rowCount() );
160  for ( int i = 0; i < mModel->rowCount(); ++i )
161  {
162  items << mModel->item( i );
163  }
164  }
165  return items;
166 }
167 
168 void QgsProcessingMultipleSelectionPanelWidget::populateList( const QVariantList &availableOptions, const QVariantList &selectedOptions )
169 {
170  mModel = new QStandardItemModel( this );
171 
172  QVariantList remainingOptions = availableOptions;
173 
174  // we add selected options first, keeping the existing order of options
175  for ( const QVariant &option : selectedOptions )
176  {
177 // if isinstance(t, QgsProcessingModelChildParameterSource):
178 // item = QStandardItem(t.staticValue())
179  // else:
180 
181  addOption( option, mValueFormatter( option ), true );
182  remainingOptions.removeAll( option );
183  }
184 
185  for ( const QVariant &option : std::as_const( remainingOptions ) )
186  {
187  addOption( option, mValueFormatter( option ), false );
188  }
189 
190  mSelectionList->setModel( mModel );
191 }
192 
193 
194 void QgsProcessingMultipleSelectionPanelWidget::addOption( const QVariant &value, const QString &title, bool selected, bool updateExistingTitle )
195 {
196  // don't add duplicate options
197  for ( int i = 0; i < mModel->rowCount(); ++i )
198  {
199  if ( mModel->item( i )->data( Qt::UserRole ) == value ||
200  ( mModel->item( i )->data( Qt::UserRole ).canConvert< QgsProcessingModelChildParameterSource >() &&
201  value.canConvert< QgsProcessingModelChildParameterSource >() &&
202  mModel->item( i )->data( Qt::UserRole ).value< QgsProcessingModelChildParameterSource >() ==
203  value.value< QgsProcessingModelChildParameterSource >() )
204  )
205  {
206  if ( updateExistingTitle )
207  mModel->item( i )->setText( title );
208  return;
209  }
210  }
211 
212  std::unique_ptr< QStandardItem > item = std::make_unique< QStandardItem >( title );
213  item->setData( value, Qt::UserRole );
214  item->setCheckState( selected ? Qt::Checked : Qt::Unchecked );
215  item->setCheckable( true );
216  item->setDropEnabled( false );
217  mModel->appendRow( item.release() );
218 }
219 
220 //
221 // QgsProcessingMultipleSelectionDialog
222 //
223 
224 
225 
226 QgsProcessingMultipleSelectionDialog::QgsProcessingMultipleSelectionDialog( const QVariantList &availableOptions, const QVariantList &selectedOptions, QWidget *parent, Qt::WindowFlags flags )
227  : QDialog( parent, flags )
228 {
229  setWindowTitle( tr( "Multiple Selection" ) );
230  QVBoxLayout *vLayout = new QVBoxLayout();
231  mWidget = new QgsProcessingMultipleSelectionPanelWidget( availableOptions, selectedOptions );
232  vLayout->addWidget( mWidget );
233  mWidget->buttonBox()->addButton( QDialogButtonBox::Cancel );
234  connect( mWidget->buttonBox(), &QDialogButtonBox::accepted, this, &QDialog::accept );
235  connect( mWidget->buttonBox(), &QDialogButtonBox::rejected, this, &QDialog::reject );
236  setLayout( vLayout );
237 }
238 
239 void QgsProcessingMultipleSelectionDialog::setValueFormatter( const std::function<QString( const QVariant & )> &formatter )
240 {
241  mWidget->setValueFormatter( formatter );
242 }
243 
244 QVariantList QgsProcessingMultipleSelectionDialog::selectedOptions() const
245 {
246  return mWidget->selectedOptions();
247 }
248 
249 
250 //
251 // QgsProcessingMultipleInputPanelWidget
252 //
253 
254 QgsProcessingMultipleInputPanelWidget::QgsProcessingMultipleInputPanelWidget( const QgsProcessingParameterMultipleLayers *parameter, const QVariantList &selectedOptions,
255  const QList<QgsProcessingModelChildParameterSource> &modelSources,
256  QgsProcessingModelAlgorithm *model, QWidget *parent )
257  : QgsProcessingMultipleSelectionPanelWidget( QVariantList(), selectedOptions, parent )
258  , mParameter( parameter )
259 {
260  QPushButton *addFileButton = new QPushButton( tr( "Add File(s)…" ) );
261  connect( addFileButton, &QPushButton::clicked, this, &QgsProcessingMultipleInputPanelWidget::addFiles );
262  buttonBox()->addButton( addFileButton, QDialogButtonBox::ActionRole );
263 
264  QPushButton *addDirButton = new QPushButton( tr( "Add Directory…" ) );
265  connect( addDirButton, &QPushButton::clicked, this, &QgsProcessingMultipleInputPanelWidget::addDirectory );
266  buttonBox()->addButton( addDirButton, QDialogButtonBox::ActionRole );
267 
268  for ( const QgsProcessingModelChildParameterSource &source : modelSources )
269  {
270  addOption( QVariant::fromValue( source ), source.friendlyIdentifier( model ), false, true );
271  }
272 }
273 
274 void QgsProcessingMultipleInputPanelWidget::setProject( QgsProject *project )
275 {
276  if ( mParameter->layerType() != QgsProcessing::TypeFile )
277  populateFromProject( project );
278 }
279 
280 void QgsProcessingMultipleInputPanelWidget::addFiles()
281 {
282  QgsSettings settings;
283  QString path = settings.value( QStringLiteral( "/Processing/LastInputPath" ), QDir::homePath() ).toString();
284 
285  QString filter;
286  if ( const QgsFileFilterGenerator *generator = dynamic_cast< const QgsFileFilterGenerator * >( mParameter ) )
287  filter = generator->createFileFilter();
288  else
289  filter = QObject::tr( "All files (*.*)" );
290 
291  const QStringList filenames = QFileDialog::getOpenFileNames( this, tr( "Select File(s)" ), path, filter );
292  if ( filenames.empty() )
293  return;
294 
295  settings.setValue( QStringLiteral( "/Processing/LastInputPath" ), QFileInfo( filenames.at( 0 ) ).path() );
296 
297  for ( const QString &file : filenames )
298  {
299  addOption( file, file, true );
300  }
301 
302  emit selectionChanged();
303 }
304 
305 void QgsProcessingMultipleInputPanelWidget::addDirectory()
306 {
307  QgsSettings settings;
308  QString path = settings.value( QStringLiteral( "/Processing/LastInputPath" ), QDir::homePath() ).toString();
309 
310  const QString dir = QFileDialog::getExistingDirectory( this, tr( "Select Directory" ), path );
311  if ( dir.isEmpty() )
312  return;
313 
314  settings.setValue( QStringLiteral( "/Processing/LastInputPath" ), dir );
315 
316  QStringList nameFilters;
317  if ( const QgsFileFilterGenerator *generator = dynamic_cast< const QgsFileFilterGenerator * >( mParameter ) )
318  {
319  const QStringList extensions = QgsFileUtils::extensionsFromFilter( generator->createFileFilter() );
320  for ( const QString &extension : extensions )
321  {
322  nameFilters << QStringLiteral( "*.%1" ).arg( extension );
323  nameFilters << QStringLiteral( "*.%1" ).arg( extension.toUpper() );
324  nameFilters << QStringLiteral( "*.%1" ).arg( extension.toLower() );
325  }
326  }
327 
328  QDirIterator it( path, nameFilters, QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot, QDirIterator::Subdirectories );
329  QStringList files;
330  while ( it.hasNext() )
331  {
332  const QString fullPath = it.next();
333  addOption( fullPath, fullPath, true );
334  }
335  emit selectionChanged();
336 }
337 
338 void QgsProcessingMultipleInputPanelWidget::populateFromProject( QgsProject *project )
339 {
340  connect( project, &QgsProject::layerRemoved, this, [&]( const QString & layerId )
341  {
342  for ( int i = 0; i < mModel->rowCount(); ++i )
343  {
344  const QStandardItem *item = mModel->item( i );
345  if ( item->data( Qt::UserRole ) == layerId )
346  {
347  bool isChecked = ( item->checkState() == Qt::Checked );
348  mModel->removeRow( i );
349 
350  if ( isChecked )
351  emit selectionChanged();
352 
353  break;
354  }
355  }
356  } );
357 
358  QgsSettings settings;
359  auto addLayer = [&]( const QgsMapLayer * layer )
360  {
361  const QString authid = layer->crs().authid();
362  QString title;
363  if ( settings.value( QStringLiteral( "Processing/Configuration/SHOW_CRS_DEF" ), true ).toBool() && !authid.isEmpty() )
364  title = QStringLiteral( "%1 [%2]" ).arg( layer->name(), authid );
365  else
366  title = layer->name();
367 
368 
369  QString id = layer->id();
370  if ( layer == project->mainAnnotationLayer() )
371  id = QStringLiteral( "main" );
372 
373  for ( int i = 0; i < mModel->rowCount(); ++i )
374  {
375  // try to match project layers to current layers
376  if ( mModel->item( i )->data( Qt::UserRole ) == layer->id() )
377  {
378  id = layer->id();
379  break;
380  }
381  else if ( mModel->item( i )->data( Qt::UserRole ) == layer->source() )
382  {
383  id = layer->source();
384  break;
385  }
386  }
387 
388  addOption( id, title, false, true );
389  };
390 
391  switch ( mParameter->layerType() )
392  {
394  break;
395 
397  {
398  const QList<QgsRasterLayer *> options = QgsProcessingUtils::compatibleRasterLayers( project, false );
399  for ( const QgsRasterLayer *layer : options )
400  {
401  addLayer( layer );
402  }
403  break;
404  }
405 
407  {
408  const QList<QgsMeshLayer *> options = QgsProcessingUtils::compatibleMeshLayers( project, false );
409  for ( const QgsMeshLayer *layer : options )
410  {
411  addLayer( layer );
412  }
413 
414  break;
415  }
416 
418  {
419  const QList<QgsPluginLayer *> options = QgsProcessingUtils::compatiblePluginLayers( project, false );
420  for ( const QgsPluginLayer *layer : options )
421  {
422  addLayer( layer );
423  }
424 
425  break;
426  }
427 
429  {
430  const QList<QgsAnnotationLayer *> options = QgsProcessingUtils::compatibleAnnotationLayers( project, false );
431  for ( const QgsAnnotationLayer *layer : options )
432  {
433  addLayer( layer );
434  }
435 
436  break;
437  }
438 
440  {
441  const QList<QgsPointCloudLayer *> options = QgsProcessingUtils::compatiblePointCloudLayers( project, false );
442  for ( const QgsPointCloudLayer *layer : options )
443  {
444  addLayer( layer );
445  }
446 
447  break;
448  }
449 
452  {
453  const QList<QgsVectorLayer *> options = QgsProcessingUtils::compatibleVectorLayers( project, QList< int >() );
454  for ( const QgsVectorLayer *layer : options )
455  {
456  addLayer( layer );
457  }
458 
459  break;
460  }
461 
463  {
464  const QList<QgsVectorLayer *> vectors = QgsProcessingUtils::compatibleVectorLayers( project, QList< int >() );
465  for ( const QgsVectorLayer *layer : vectors )
466  {
467  addLayer( layer );
468  }
469  const QList<QgsRasterLayer *> rasters = QgsProcessingUtils::compatibleRasterLayers( project );
470  for ( const QgsRasterLayer *layer : rasters )
471  {
472  addLayer( layer );
473  }
474  const QList<QgsMeshLayer *> meshes = QgsProcessingUtils::compatibleMeshLayers( project );
475  for ( const QgsMeshLayer *layer : meshes )
476  {
477  addLayer( layer );
478  }
479  const QList<QgsPluginLayer *> plugins = QgsProcessingUtils::compatiblePluginLayers( project );
480  for ( const QgsPluginLayer *layer : plugins )
481  {
482  addLayer( layer );
483  }
484  const QList<QgsPointCloudLayer *> pointClouds = QgsProcessingUtils::compatiblePointCloudLayers( project );
485  for ( const QgsPointCloudLayer *layer : pointClouds )
486  {
487  addLayer( layer );
488  }
489  const QList<QgsAnnotationLayer *> annotations = QgsProcessingUtils::compatibleAnnotationLayers( project );
490  for ( const QgsAnnotationLayer *layer : annotations )
491  {
492  addLayer( layer );
493  }
494 
495  break;
496  }
497 
501  {
502  const QList<QgsVectorLayer *> vectors = QgsProcessingUtils::compatibleVectorLayers( project, QList< int >() << mParameter->layerType() );
503  for ( const QgsVectorLayer *layer : vectors )
504  {
505  addLayer( layer );
506  }
507  break;
508  }
509  }
510 }
511 
512 //
513 // QgsProcessingMultipleInputDialog
514 //
515 
516 QgsProcessingMultipleInputDialog::QgsProcessingMultipleInputDialog( const QgsProcessingParameterMultipleLayers *parameter, const QVariantList &selectedOptions,
517  const QList< QgsProcessingModelChildParameterSource > &modelSources, QgsProcessingModelAlgorithm *model, QWidget *parent, Qt::WindowFlags flags )
518  : QDialog( parent, flags )
519 {
520  setWindowTitle( tr( "Multiple Selection" ) );
521  QVBoxLayout *vLayout = new QVBoxLayout();
522  mWidget = new QgsProcessingMultipleInputPanelWidget( parameter, selectedOptions, modelSources, model );
523  vLayout->addWidget( mWidget );
524  mWidget->buttonBox()->addButton( QDialogButtonBox::Cancel );
525  connect( mWidget->buttonBox(), &QDialogButtonBox::accepted, this, &QDialog::accept );
526  connect( mWidget->buttonBox(), &QDialogButtonBox::rejected, this, &QDialog::reject );
527  setLayout( vLayout );
528 }
529 
530 QVariantList QgsProcessingMultipleInputDialog::selectedOptions() const
531 {
532  return mWidget->selectedOptions();
533 }
534 
535 void QgsProcessingMultipleInputDialog::setProject( QgsProject *project )
536 {
537  mWidget->setProject( project );
538 }
539 
540 
Represents a map layer containing a set of georeferenced annotations, e.g.
Abstract interface for classes which generate a file filter string.
static QStringList extensionsFromFilter(const QString &filter)
Returns a list of the extensions contained within a file filter string.
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:168
Base class for all map layer types.
Definition: qgsmaplayer.h:73
Represents a mesh layer supporting display of data on structured or unstructured meshes.
Definition: qgsmeshlayer.h:97
Base class for any widget that can be shown as a inline panel.
Base class for plugin layers.
Represents a map layer supporting display of point clouds.
A parameter for processing algorithms which accepts multiple map layers.
static QList< QgsAnnotationLayer * > compatibleAnnotationLayers(QgsProject *project, bool sort=true)
Returns a list of annotation layers from a project which are compatible with the processing framework...
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.
static QList< QgsPluginLayer * > compatiblePluginLayers(QgsProject *project, bool sort=true)
Returns a list of plugin layers from a project which are compatible with the processing framework.
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.
static QList< QgsPointCloudLayer * > compatiblePointCloudLayers(QgsProject *project, bool sort=true)
Returns a list of point cloud layers from a project which are compatible with the processing framewor...
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.
@ TypePlugin
Plugin layers.
Definition: qgsprocessing.h:56
@ TypeVectorLine
Vector line layers.
Definition: qgsprocessing.h:50
@ TypeMapLayer
Any map layer type (raster, vector, mesh, point cloud, annotation or plugin layer)
Definition: qgsprocessing.h:47
@ TypeVectorPolygon
Vector polygon layers.
Definition: qgsprocessing.h:51
@ TypeFile
Files (i.e. non map layer sources, such as text files)
Definition: qgsprocessing.h:53
@ TypeAnnotation
Annotation layers.
Definition: qgsprocessing.h:58
@ TypePointCloud
Point cloud layers.
Definition: qgsprocessing.h:57
@ TypeMesh
Mesh layers.
Definition: qgsprocessing.h:55
@ TypeVector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:54
@ TypeRaster
Raster layers.
Definition: qgsprocessing.h:52
@ TypeVectorPoint
Vector point layers.
Definition: qgsprocessing.h:49
@ TypeVectorAnyGeometry
Any vector layer with geometry.
Definition: qgsprocessing.h:48
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:101
QgsAnnotationLayer * mainAnnotationLayer()
Returns the main annotation layer associated with the project.
void layerRemoved(const QString &layerId)
Emitted after a layer was removed from the registry.
Represents a raster layer.
Represents a vector layer which manages a vector based data sets.