QGIS API Documentation 3.27.0-Master (f261cc1f8b)
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.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
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.canConvert< 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.canConvert< 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 ).canConvert< QgsProcessingModelChildParameterSource >() &&
207 value.canConvert< 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 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( path, nameFilters, QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot, QDirIterator::Subdirectories );
335 QStringList files;
336 while ( it.hasNext() )
337 {
338 const QString fullPath = it.next();
339 addOption( fullPath, fullPath, true );
340 }
341 emit selectionChanged();
342}
343
344void QgsProcessingMultipleInputPanelWidget::populateFromProject( QgsProject *project )
345{
346 connect( project, &QgsProject::layerRemoved, this, [&]( const QString & layerId )
347 {
348 for ( int i = 0; i < mModel->rowCount(); ++i )
349 {
350 const QStandardItem *item = mModel->item( i );
351 if ( item->data( Qt::UserRole ) == layerId )
352 {
353 bool isChecked = ( item->checkState() == Qt::Checked );
354 mModel->removeRow( i );
355
356 if ( isChecked )
357 emit selectionChanged();
358
359 break;
360 }
361 }
362 } );
363
364 QgsSettings settings;
365 auto addLayer = [&]( const QgsMapLayer * layer )
366 {
367 const QString authid = layer->crs().authid();
368 QString title;
369 if ( settings.value( QStringLiteral( "Processing/Configuration/SHOW_CRS_DEF" ), true ).toBool() && !authid.isEmpty() )
370 title = QStringLiteral( "%1 [%2]" ).arg( layer->name(), authid );
371 else
372 title = layer->name();
373
374
375 QString id = layer->id();
376 if ( layer == project->mainAnnotationLayer() )
377 id = QStringLiteral( "main" );
378
379 for ( int i = 0; i < mModel->rowCount(); ++i )
380 {
381 // try to match project layers to current layers
382 if ( mModel->item( i )->data( Qt::UserRole ) == layer->id() )
383 {
384 id = layer->id();
385 break;
386 }
387 else if ( mModel->item( i )->data( Qt::UserRole ) == layer->source() )
388 {
389 id = layer->source();
390 break;
391 }
392 }
393
394 addOption( id, title, false, true );
395 };
396
397 switch ( mParameter->layerType() )
398 {
400 break;
401
403 {
404 const QList<QgsRasterLayer *> options = QgsProcessingUtils::compatibleRasterLayers( project, false );
405 for ( const QgsRasterLayer *layer : options )
406 {
407 addLayer( layer );
408 }
409 break;
410 }
411
413 {
414 const QList<QgsMeshLayer *> options = QgsProcessingUtils::compatibleMeshLayers( project, false );
415 for ( const QgsMeshLayer *layer : options )
416 {
417 addLayer( layer );
418 }
419
420 break;
421 }
422
424 {
425 const QList<QgsPluginLayer *> options = QgsProcessingUtils::compatiblePluginLayers( project, false );
426 for ( const QgsPluginLayer *layer : options )
427 {
428 addLayer( layer );
429 }
430
431 break;
432 }
433
435 {
436 const QList<QgsAnnotationLayer *> options = QgsProcessingUtils::compatibleAnnotationLayers( project, false );
437 for ( const QgsAnnotationLayer *layer : options )
438 {
439 addLayer( layer );
440 }
441
442 break;
443 }
444
446 {
447 const QList<QgsPointCloudLayer *> options = QgsProcessingUtils::compatiblePointCloudLayers( project, false );
448 for ( const QgsPointCloudLayer *layer : options )
449 {
450 addLayer( layer );
451 }
452
453 break;
454 }
455
458 {
459 const QList<QgsVectorLayer *> options = QgsProcessingUtils::compatibleVectorLayers( project, QList< int >() );
460 for ( const QgsVectorLayer *layer : options )
461 {
462 addLayer( layer );
463 }
464
465 break;
466 }
467
469 {
470 const QList<QgsVectorLayer *> vectors = QgsProcessingUtils::compatibleVectorLayers( project, QList< int >() );
471 for ( const QgsVectorLayer *layer : vectors )
472 {
473 addLayer( layer );
474 }
475 const QList<QgsRasterLayer *> rasters = QgsProcessingUtils::compatibleRasterLayers( project );
476 for ( const QgsRasterLayer *layer : rasters )
477 {
478 addLayer( layer );
479 }
480 const QList<QgsMeshLayer *> meshes = QgsProcessingUtils::compatibleMeshLayers( project );
481 for ( const QgsMeshLayer *layer : meshes )
482 {
483 addLayer( layer );
484 }
485 const QList<QgsPluginLayer *> plugins = QgsProcessingUtils::compatiblePluginLayers( project );
486 for ( const QgsPluginLayer *layer : plugins )
487 {
488 addLayer( layer );
489 }
490 const QList<QgsPointCloudLayer *> pointClouds = QgsProcessingUtils::compatiblePointCloudLayers( project );
491 for ( const QgsPointCloudLayer *layer : pointClouds )
492 {
493 addLayer( layer );
494 }
495 const QList<QgsAnnotationLayer *> annotations = QgsProcessingUtils::compatibleAnnotationLayers( project );
496 for ( const QgsAnnotationLayer *layer : annotations )
497 {
498 addLayer( layer );
499 }
500
501 break;
502 }
503
507 {
508 const QList<QgsVectorLayer *> vectors = QgsProcessingUtils::compatibleVectorLayers( project, QList< int >() << mParameter->layerType() );
509 for ( const QgsVectorLayer *layer : vectors )
510 {
511 addLayer( layer );
512 }
513 break;
514 }
515 }
516}
517
518//
519// QgsProcessingMultipleInputDialog
520//
521
522QgsProcessingMultipleInputDialog::QgsProcessingMultipleInputDialog( const QgsProcessingParameterMultipleLayers *parameter, const QVariantList &selectedOptions,
523 const QList< QgsProcessingModelChildParameterSource > &modelSources, QgsProcessingModelAlgorithm *model, QWidget *parent, Qt::WindowFlags flags )
524 : QDialog( parent, flags )
525{
526 setWindowTitle( tr( "Multiple Selection" ) );
527 QVBoxLayout *vLayout = new QVBoxLayout();
528 mWidget = new QgsProcessingMultipleInputPanelWidget( parameter, selectedOptions, modelSources, model );
529 vLayout->addWidget( mWidget );
530 mWidget->buttonBox()->addButton( QDialogButtonBox::Cancel );
531 connect( mWidget->buttonBox(), &QDialogButtonBox::accepted, this, &QDialog::accept );
532 connect( mWidget->buttonBox(), &QDialogButtonBox::rejected, this, &QDialog::reject );
533 setLayout( vLayout );
534}
535
536QVariantList QgsProcessingMultipleInputDialog::selectedOptions() const
537{
538 return mWidget->selectedOptions();
539}
540
541void QgsProcessingMultipleInputDialog::setProject( QgsProject *project )
542{
543 mWidget->setProject( project );
544}
545
546
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:180
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:99
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:104
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:62
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.