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