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