1 /***************************************************************************
2 qgsprocessingmaplayercombobox.cpp
3 -------------------------------
4 begin : June 2019
5 copyright : (C) 2019 by 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
16 #include "qgsprocessingmaplayercombobox.h"
17 #include "qgsmaplayercombobox.h"
18 #include "qgsmimedatautils.h"
19 #include "qgsprocessingparameters.h"
20 #include "qgssettings.h"
21 #include "qgsvectorlayer.h"
22 #include "qgsfeatureid.h"
23 #include "qgsapplication.h"
24 #include "qgsguiutils.h"
25 #include "qgspanelwidget.h"
26 #include "qgsprocessingfeaturesourceoptionswidget.h"
27 #include "qgsdatasourceselectdialog.h"
28 #include "qgsprocessingwidgetwrapper.h"
29 #include <QHBoxLayout>
30 #include <QVBoxLayout>
31 #include <QToolButton>
32 #include <QCheckBox>
33 #include <QDragEnterEvent>
34 #include <QMenu>
35 #include <QAction>
36 #include <QFileDialog>
37
38 ///@cond PRIVATE
39
QgsProcessingMapLayerComboBox(const QgsProcessingParameterDefinition * parameter,QgsProcessingGui::WidgetType type,QWidget * parent)40 QgsProcessingMapLayerComboBox::QgsProcessingMapLayerComboBox( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type, QWidget *parent )
41 : QWidget( parent )
42 , mParameter( parameter->clone() )
43 {
44 QHBoxLayout *layout = new QHBoxLayout();
45 layout->setContentsMargins( 0, 0, 0, 0 );
46 layout->setSpacing( 6 );
47
48 mCombo = new QgsMapLayerComboBox();
49 layout->addWidget( mCombo );
50 layout->setAlignment( mCombo, Qt::AlignTop );
51
52 int iconSize = QgsGuiUtils::scaleIconSize( 24 );
53 if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() && type == QgsProcessingGui::Standard )
54 {
55 mIterateButton = new QToolButton();
56 mIterateButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconIterate.svg" ) ) );
57 mIterateButton->setToolTip( tr( "Iterate over this layer, creating a separate output for every feature in the layer" ) );
58 mIterateButton->setCheckable( true );
59 mIterateButton->setAutoRaise( true );
60
61 // button width is 1.25 * icon size, height 1.1 * icon size. But we round to ensure even pixel sizes for equal margins
62 mIterateButton->setFixedSize( 2 * static_cast< int >( 1.25 * iconSize / 2.0 ), 2 * static_cast< int >( iconSize * 1.1 / 2.0 ) );
63 mIterateButton->setIconSize( QSize( iconSize, iconSize ) );
64
65 layout->addWidget( mIterateButton );
66 layout->setAlignment( mIterateButton, Qt::AlignTop );
67 }
68
69 if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() )
70 {
71 mSettingsButton = new QToolButton();
72 mSettingsButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionOptions.svg" ) ) );
73 mSettingsButton->setToolTip( tr( "Advanced options" ) );
74
75 // button width is 1.25 * icon size, height 1.1 * icon size. But we round to ensure even pixel sizes for equal margins
76 mSettingsButton->setFixedSize( 2 * static_cast< int >( 1.25 * iconSize / 2.0 ), 2 * static_cast< int >( iconSize * 1.1 / 2.0 ) );
77 mSettingsButton->setIconSize( QSize( iconSize, iconSize ) );
78 mSettingsButton->setAutoRaise( true );
79
80 connect( mSettingsButton, &QToolButton::clicked, this, &QgsProcessingMapLayerComboBox::showSourceOptions );
81 layout->addWidget( mSettingsButton );
82 layout->setAlignment( mSettingsButton, Qt::AlignTop );
83 }
84
85 mSelectButton = new QToolButton();
86 mSelectButton->setText( QString( QChar( 0x2026 ) ) );
87 mSelectButton->setToolTip( tr( "Select input" ) );
88 layout->addWidget( mSelectButton );
89 layout->setAlignment( mSelectButton, Qt::AlignTop );
90 if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() || mParameter->type() == QgsProcessingParameterVectorLayer::typeName() )
91 {
92 mFeatureSourceMenu = new QMenu( this );
93 QAction *selectFromFileAction = new QAction( tr( "Select File…" ), mFeatureSourceMenu );
94 connect( selectFromFileAction, &QAction::triggered, this, &QgsProcessingMapLayerComboBox::selectFromFile );
95 mFeatureSourceMenu->addAction( selectFromFileAction );
96 QAction *browseForLayerAction = new QAction( tr( "Browse for Layer…" ), mFeatureSourceMenu );
97 connect( browseForLayerAction, &QAction::triggered, this, &QgsProcessingMapLayerComboBox::browseForLayer );
98 mFeatureSourceMenu->addAction( browseForLayerAction );
99 mSelectButton->setMenu( mFeatureSourceMenu );
100 mSelectButton->setPopupMode( QToolButton::InstantPopup );
101 }
102 else
103 {
104 connect( mSelectButton, &QToolButton::clicked, this, &QgsProcessingMapLayerComboBox::selectFromFile );
105 }
106
107 QVBoxLayout *vl = new QVBoxLayout();
108 vl->setContentsMargins( 0, 0, 0, 0 );
109 vl->setSpacing( 6 );
110 vl->addLayout( layout );
111
112 QgsMapLayerProxyModel::Filters filters = QgsMapLayerProxyModel::Filters();
113
114 if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() && type == QgsProcessingGui::Standard )
115 {
116 mUseSelectionCheckBox = new QCheckBox( tr( "Selected features only" ) );
117 mUseSelectionCheckBox->setChecked( false );
118 mUseSelectionCheckBox->setEnabled( false );
119 vl->addWidget( mUseSelectionCheckBox );
120 }
121
122 if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() || mParameter->type() == QgsProcessingParameterVectorLayer::typeName() )
123 {
124 QList<int> dataTypes;
125 if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() )
126 dataTypes = static_cast< QgsProcessingParameterFeatureSource *>( mParameter.get() )->dataTypes();
127 else if ( mParameter->type() == QgsProcessingParameterVectorLayer::typeName() )
128 dataTypes = static_cast< QgsProcessingParameterVectorLayer *>( mParameter.get() )->dataTypes();
129
130 if ( dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.isEmpty() )
131 filters = QgsMapLayerProxyModel::HasGeometry;
132 if ( dataTypes.contains( QgsProcessing::TypeVectorPoint ) )
133 filters |= QgsMapLayerProxyModel::PointLayer;
134 if ( dataTypes.contains( QgsProcessing::TypeVectorLine ) )
135 filters |= QgsMapLayerProxyModel::LineLayer;
136 if ( dataTypes.contains( QgsProcessing::TypeVectorPolygon ) )
137 filters |= QgsMapLayerProxyModel::PolygonLayer;
138 if ( !filters )
139 filters = QgsMapLayerProxyModel::VectorLayer;
140 }
141 else if ( mParameter->type() == QgsProcessingParameterRasterLayer::typeName() )
142 {
143 filters = QgsMapLayerProxyModel::RasterLayer;
144 }
145 else if ( mParameter->type() == QgsProcessingParameterMeshLayer::typeName() )
146 {
147 filters = QgsMapLayerProxyModel::MeshLayer;
148 }
149 else if ( mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
150 {
151 QList<int> dataTypes;
152 dataTypes = static_cast< QgsProcessingParameterMapLayer *>( mParameter.get() )->dataTypes();
153
154 if ( dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) )
155 filters |= QgsMapLayerProxyModel::HasGeometry;
156 if ( dataTypes.contains( QgsProcessing::TypeVectorPoint ) )
157 filters |= QgsMapLayerProxyModel::PointLayer;
158 if ( dataTypes.contains( QgsProcessing::TypeVectorLine ) )
159 filters |= QgsMapLayerProxyModel::LineLayer;
160 if ( dataTypes.contains( QgsProcessing::TypeVectorPolygon ) )
161 filters |= QgsMapLayerProxyModel::PolygonLayer;
162 if ( dataTypes.contains( QgsProcessing::TypeRaster ) )
163 filters |= QgsMapLayerProxyModel::RasterLayer;
164 if ( dataTypes.contains( QgsProcessing::TypeMesh ) )
165 filters |= QgsMapLayerProxyModel::MeshLayer;
166 if ( !filters )
167 filters = QgsMapLayerProxyModel::All;
168 }
169
170 QgsSettings settings;
171 if ( settings.value( QStringLiteral( "Processing/Configuration/SHOW_CRS_DEF" ), true ).toBool() )
172 mCombo->setShowCrs( true );
173
174 if ( filters )
175 mCombo->setFilters( filters );
176 mCombo->setExcludedProviders( QStringList() << QStringLiteral( "grass" ) ); // not sure if this is still required...
177
178 if ( mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
179 {
180 mCombo->setAllowEmptyLayer( true );
181 mCombo->setLayer( nullptr );
182 }
183
184 connect( mCombo, &QgsMapLayerComboBox::layerChanged, this, &QgsProcessingMapLayerComboBox::onLayerChanged );
185 if ( mUseSelectionCheckBox )
186 connect( mUseSelectionCheckBox, &QCheckBox::toggled, this, [ = ]
187 {
188 if ( !mBlockChangedSignal )
189 emit valueChanged();
190 } );
191
192 setLayout( vl );
193
194 setAcceptDrops( true );
195
196 onLayerChanged( mCombo->currentLayer() );
197 }
198
199 QgsProcessingMapLayerComboBox::~QgsProcessingMapLayerComboBox() = default;
200
setLayer(QgsMapLayer * layer)201 void QgsProcessingMapLayerComboBox::setLayer( QgsMapLayer *layer )
202 {
203 if ( layer || mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
204 mCombo->setLayer( layer );
205 }
206
currentLayer()207 QgsMapLayer *QgsProcessingMapLayerComboBox::currentLayer()
208 {
209 return mCombo->currentLayer();
210 }
211
currentText()212 QString QgsProcessingMapLayerComboBox::currentText()
213 {
214 return mCombo->currentText();
215 }
216
setValue(const QVariant & value,QgsProcessingContext & context)217 void QgsProcessingMapLayerComboBox::setValue( const QVariant &value, QgsProcessingContext &context )
218 {
219 if ( !value.isValid() && mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
220 {
221 setLayer( nullptr );
222 return;
223 }
224
225 QVariant val = value;
226 bool found = false;
227 bool selectedOnly = false;
228 bool iterate = false;
229 if ( val.canConvert<QgsProcessingFeatureSourceDefinition>() )
230 {
231 QgsProcessingFeatureSourceDefinition fromVar = qvariant_cast<QgsProcessingFeatureSourceDefinition>( val );
232 val = fromVar.source;
233 selectedOnly = fromVar.selectedFeaturesOnly;
234 iterate = fromVar.flags & QgsProcessingFeatureSourceDefinition::Flag::FlagCreateIndividualOutputPerInputFeature;
235 mFeatureLimit = fromVar.featureLimit;
236 mIsOverridingDefaultGeometryCheck = fromVar.flags & QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck;
237 mGeometryCheck = fromVar.geometryCheck;
238 }
239 else
240 {
241 mFeatureLimit = -1;
242 mIsOverridingDefaultGeometryCheck = false;
243 mGeometryCheck = QgsFeatureRequest::GeometryAbortOnInvalid;
244 }
245
246 if ( val.canConvert<QgsProperty>() )
247 {
248 if ( val.value< QgsProperty >().propertyType() == QgsProperty::StaticProperty )
249 {
250 val = val.value< QgsProperty >().staticValue();
251 }
252 else
253 {
254 val = val.value< QgsProperty >().valueAsString( context.expressionContext(), mParameter->defaultValueForGui().toString() );
255 }
256 }
257
258 QgsMapLayer *layer = qobject_cast< QgsMapLayer * >( val.value< QObject * >() );
259 if ( !layer && val.type() == QVariant::String )
260 {
261 layer = QgsProcessingUtils::mapLayerFromString( val.toString(), context, false );
262 }
263
264 if ( layer )
265 {
266 mBlockChangedSignal++;
267 QgsMapLayer *prevLayer = currentLayer();
268 setLayer( layer );
269 found = static_cast< bool >( currentLayer() );
270 bool changed = found && ( currentLayer() != prevLayer );
271 if ( found && mUseSelectionCheckBox )
272 {
273 const bool hasSelection = qobject_cast< QgsVectorLayer * >( layer ) && qobject_cast< QgsVectorLayer * >( layer )->selectedFeatureCount() > 0;
274 changed = changed | ( ( hasSelection && selectedOnly ) != mUseSelectionCheckBox->isChecked() );
275 if ( hasSelection )
276 {
277 mUseSelectionCheckBox->setEnabled( true );
278 mUseSelectionCheckBox->setChecked( selectedOnly );
279 }
280 else
281 {
282 mUseSelectionCheckBox->setChecked( false );
283 mUseSelectionCheckBox->setEnabled( false );
284 }
285
286 if ( mIterateButton )
287 {
288 mIterateButton->setChecked( iterate );
289 }
290 }
291 mBlockChangedSignal--;
292 if ( changed )
293 emit valueChanged(); // and ensure we only ever raise one
294 }
295
296 if ( !found )
297 {
298 const QString string = val.toString();
299 if ( mIterateButton )
300 mIterateButton->setChecked( iterate );
301
302 if ( !string.isEmpty() )
303 {
304 mBlockChangedSignal++;
305 if ( mCombo->findText( string ) < 0 )
306 {
307 QStringList additional = mCombo->additionalItems();
308 additional.append( string );
309 mCombo->setAdditionalItems( additional );
310 }
311 mCombo->setCurrentIndex( mCombo->findText( string ) ); // this may or may not throw a signal, so let's block it..
312 if ( mUseSelectionCheckBox )
313 {
314 mUseSelectionCheckBox->setChecked( false );
315 mUseSelectionCheckBox->setEnabled( false );
316 }
317 mBlockChangedSignal--;
318 if ( !mBlockChangedSignal )
319 emit valueChanged(); // and ensure we only ever raise one
320 }
321 else if ( mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
322 {
323 mCombo->setLayer( nullptr );
324 if ( mUseSelectionCheckBox )
325 {
326 mUseSelectionCheckBox->setChecked( false );
327 mUseSelectionCheckBox->setEnabled( false );
328 }
329 }
330 }
331 }
332
value() const333 QVariant QgsProcessingMapLayerComboBox::value() const
334 {
335 if ( isEditable() && mCombo->currentText() != mCombo->itemText( mCombo->currentIndex() ) )
336 return mCombo->currentText();
337
338 const bool iterate = mIterateButton && mIterateButton->isChecked();
339 const bool selectedOnly = mUseSelectionCheckBox && mUseSelectionCheckBox->isChecked();
340 if ( QgsMapLayer *layer = mCombo->currentLayer() )
341 {
342 if ( selectedOnly || iterate || mFeatureLimit != -1 || mIsOverridingDefaultGeometryCheck )
343 return QgsProcessingFeatureSourceDefinition( layer->id(), selectedOnly, mFeatureLimit,
344 ( iterate ? QgsProcessingFeatureSourceDefinition::Flag::FlagCreateIndividualOutputPerInputFeature : QgsProcessingFeatureSourceDefinition::Flags() )
345 | ( mIsOverridingDefaultGeometryCheck ? QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck : QgsProcessingFeatureSourceDefinition::Flags() ),
346 mGeometryCheck );
347 else
348 return layer->id();
349 }
350 else
351 {
352 if ( !mCombo->currentText().isEmpty() )
353 {
354 if ( selectedOnly || iterate || mFeatureLimit != -1 || mIsOverridingDefaultGeometryCheck )
355 return QgsProcessingFeatureSourceDefinition( mCombo->currentText(), selectedOnly, mFeatureLimit,
356 ( iterate ? QgsProcessingFeatureSourceDefinition::Flag::FlagCreateIndividualOutputPerInputFeature : QgsProcessingFeatureSourceDefinition::Flags() )
357 | ( mIsOverridingDefaultGeometryCheck ? QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck : QgsProcessingFeatureSourceDefinition::Flags() ),
358 mGeometryCheck );
359 else
360 return mCombo->currentText();
361 }
362 }
363 return QVariant();
364 }
365
setWidgetContext(const QgsProcessingParameterWidgetContext & context)366 void QgsProcessingMapLayerComboBox::setWidgetContext( const QgsProcessingParameterWidgetContext &context )
367 {
368 mBrowserModel = context.browserModel();
369 }
370
setEditable(bool editable)371 void QgsProcessingMapLayerComboBox::setEditable( bool editable )
372 {
373 mCombo->setEditable( editable );
374 }
375
isEditable() const376 bool QgsProcessingMapLayerComboBox::isEditable() const
377 {
378 return mCombo->isEditable();
379 }
380
compatibleMapLayerFromMimeData(const QMimeData * data,bool & incompatibleLayerSelected) const381 QgsMapLayer *QgsProcessingMapLayerComboBox::compatibleMapLayerFromMimeData( const QMimeData *data, bool &incompatibleLayerSelected ) const
382 {
383 incompatibleLayerSelected = false;
384 const QgsMimeDataUtils::UriList uriList = QgsMimeDataUtils::decodeUriList( data );
385 for ( const QgsMimeDataUtils::Uri &u : uriList )
386 {
387 // is this uri from the current project?
388 if ( QgsMapLayer *layer = u.mapLayer() )
389 {
390 if ( mCombo->mProxyModel->acceptsLayer( layer ) )
391 return layer;
392 else
393 {
394 incompatibleLayerSelected = true;
395 return nullptr;
396 }
397 }
398 }
399 return nullptr;
400 }
401
402
compatibleUriFromMimeData(const QMimeData * data) const403 QString QgsProcessingMapLayerComboBox::compatibleUriFromMimeData( const QMimeData *data ) const
404 {
405 const QgsMimeDataUtils::UriList uriList = QgsMimeDataUtils::decodeUriList( data );
406 for ( const QgsMimeDataUtils::Uri &u : uriList )
407 {
408 if ( ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName()
409 || mParameter->type() == QgsProcessingParameterVectorLayer::typeName() )
410 && u.layerType == QLatin1String( "vector" ) )
411 {
412 QList< int > dataTypes = mParameter->type() == QgsProcessingParameterFeatureSource::typeName() ? static_cast< QgsProcessingParameterFeatureSource * >( mParameter.get() )->dataTypes()
413 : ( mParameter->type() == QgsProcessingParameterVectorLayer::typeName() ? static_cast<QgsProcessingParameterVectorLayer *>( mParameter.get() )->dataTypes()
414 : QList< int >() );
415 bool acceptable = false;
416 switch ( QgsWkbTypes::geometryType( u.wkbType ) )
417 {
418 case QgsWkbTypes::UnknownGeometry:
419 acceptable = true;
420 break;
421
422 case QgsWkbTypes::PointGeometry:
423 if ( dataTypes.isEmpty() || dataTypes.contains( QgsProcessing::TypeVector ) || dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.contains( QgsProcessing::TypeVectorPoint ) )
424 acceptable = true;
425 break;
426
427 case QgsWkbTypes::LineGeometry:
428 if ( dataTypes.isEmpty() || dataTypes.contains( QgsProcessing::TypeVector ) || dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.contains( QgsProcessing::TypeVectorLine ) )
429 acceptable = true;
430 break;
431
432 case QgsWkbTypes::PolygonGeometry:
433 if ( dataTypes.isEmpty() || dataTypes.contains( QgsProcessing::TypeVector ) || dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.contains( QgsProcessing::TypeVectorPolygon ) )
434 acceptable = true;
435 break;
436
437 case QgsWkbTypes::NullGeometry:
438 if ( dataTypes.contains( QgsProcessing::TypeVector ) )
439 acceptable = true;
440 break;
441 }
442 if ( acceptable )
443 return u.providerKey != QLatin1String( "ogr" ) ? QgsProcessingUtils::encodeProviderKeyAndUri( u.providerKey, u.uri ) : u.uri;
444 }
445 else if ( mParameter->type() == QgsProcessingParameterRasterLayer::typeName()
446 && u.layerType == QLatin1String( "raster" ) && u.providerKey == QLatin1String( "gdal" ) )
447 return u.uri;
448 else if ( mParameter->type() == QgsProcessingParameterMeshLayer::typeName()
449 && u.layerType == QLatin1String( "mesh" ) && u.providerKey == QLatin1String( "mdal" ) )
450 return u.uri;
451 else if ( mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
452 {
453 QList< int > dataTypes = static_cast< QgsProcessingParameterMapLayer * >( mParameter.get() )->dataTypes();
454 if ( dataTypes.isEmpty() || dataTypes.contains( QgsProcessing::TypeMapLayer ) )
455 {
456 return u.uri;
457 }
458
459 if ( u.layerType == QLatin1String( "vector" ) && u.providerKey == QLatin1String( "ogr" ) )
460 {
461 switch ( QgsWkbTypes::geometryType( u.wkbType ) )
462 {
463 case QgsWkbTypes::UnknownGeometry:
464 return u.uri;
465
466 case QgsWkbTypes::PointGeometry:
467 if ( dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.contains( QgsProcessing::TypeVectorPoint ) )
468 return u.uri;
469 break;
470
471 case QgsWkbTypes::LineGeometry:
472 if ( dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.contains( QgsProcessing::TypeVectorLine ) )
473 return u.uri;
474 break;
475
476 case QgsWkbTypes::PolygonGeometry:
477 if ( dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.contains( QgsProcessing::TypeVectorPolygon ) )
478 return u.uri;
479 break;
480
481 case QgsWkbTypes::NullGeometry:
482 return u.uri;
483 }
484 }
485 else if ( u.layerType == QLatin1String( "raster" ) && u.providerKey == QLatin1String( "gdal" )
486 && dataTypes.contains( QgsProcessing::TypeRaster ) )
487 return u.uri;
488 else if ( u.layerType == QLatin1String( "mesh" ) && u.providerKey == QLatin1String( "mdal" )
489 && dataTypes.contains( QgsProcessing::TypeMesh ) )
490 return u.uri;
491 }
492 }
493 if ( !uriList.isEmpty() )
494 return QString();
495
496 // second chance -- files dragged from file explorer, outside of QGIS
497 QStringList rawPaths;
498 if ( data->hasUrls() )
499 {
500 const QList< QUrl > urls = data->urls();
501 rawPaths.reserve( urls.count() );
502 for ( const QUrl &url : urls )
503 {
504 const QString local = url.toLocalFile();
505 if ( !rawPaths.contains( local ) )
506 rawPaths.append( local );
507 }
508 }
509 if ( !data->text().isEmpty() && !rawPaths.contains( data->text() ) )
510 rawPaths.append( data->text() );
511
512 for ( const QString &path : qgis::as_const( rawPaths ) )
513 {
514 QFileInfo file( path );
515 if ( file.isFile() )
516 {
517 // TODO - we should check to see if it's a valid extension for the parameter, but that's non-trivial
518 return path;
519 }
520 }
521
522 return QString();
523 }
524
dragEnterEvent(QDragEnterEvent * event)525 void QgsProcessingMapLayerComboBox::dragEnterEvent( QDragEnterEvent *event )
526 {
527 if ( !( event->possibleActions() & Qt::CopyAction ) )
528 return;
529
530 bool incompatibleLayerSelected = false;
531 QgsMapLayer *layer = compatibleMapLayerFromMimeData( event->mimeData(), incompatibleLayerSelected );
532 const QString uri = compatibleUriFromMimeData( event->mimeData() );
533 if ( layer || ( !incompatibleLayerSelected && !uri.isEmpty() ) )
534 {
535 // dragged an acceptable layer, phew
536 event->setDropAction( Qt::CopyAction );
537 event->accept();
538 mDragActive = true;
539 mCombo->mHighlight = true;
540 update();
541 }
542 }
543
dragLeaveEvent(QDragLeaveEvent * event)544 void QgsProcessingMapLayerComboBox::dragLeaveEvent( QDragLeaveEvent *event )
545 {
546 QWidget::dragLeaveEvent( event );
547 if ( mDragActive )
548 {
549 event->accept();
550 mDragActive = false;
551 mCombo->mHighlight = false;
552 update();
553 }
554 }
555
dropEvent(QDropEvent * event)556 void QgsProcessingMapLayerComboBox::dropEvent( QDropEvent *event )
557 {
558 if ( !( event->possibleActions() & Qt::CopyAction ) )
559 return;
560
561 bool incompatibleLayerSelected = false;
562 QgsMapLayer *layer = compatibleMapLayerFromMimeData( event->mimeData(), incompatibleLayerSelected );
563 const QString uri = compatibleUriFromMimeData( event->mimeData() );
564 if ( layer || ( !incompatibleLayerSelected && !uri.isEmpty() ) )
565 {
566 // dropped an acceptable layer, phew
567 setFocus( Qt::MouseFocusReason );
568 event->setDropAction( Qt::CopyAction );
569 event->accept();
570 QgsProcessingContext context;
571 setValue( layer ? QVariant::fromValue( layer ) : QVariant::fromValue( uri ), context );
572 }
573 mDragActive = false;
574 mCombo->mHighlight = false;
575 update();
576 }
577
onLayerChanged(QgsMapLayer * layer)578 void QgsProcessingMapLayerComboBox::onLayerChanged( QgsMapLayer *layer )
579 {
580 if ( mUseSelectionCheckBox && mParameter->type() == QgsProcessingParameterFeatureSource::typeName() )
581 {
582 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
583 {
584 if ( QgsVectorLayer *prevLayer = qobject_cast< QgsVectorLayer * >( mPrevLayer ) )
585 {
586 disconnect( prevLayer, &QgsVectorLayer::selectionChanged, this, &QgsProcessingMapLayerComboBox::selectionChanged );
587 }
588 if ( vl->selectedFeatureCount() == 0 )
589 mUseSelectionCheckBox->setChecked( false );
590 mUseSelectionCheckBox->setEnabled( vl->selectedFeatureCount() > 0 );
591 connect( vl, &QgsVectorLayer::selectionChanged, this, &QgsProcessingMapLayerComboBox::selectionChanged );
592 }
593 }
594
595 mPrevLayer = layer;
596 if ( !mBlockChangedSignal )
597 emit valueChanged();
598 }
599
selectionChanged(const QgsFeatureIds & selected,const QgsFeatureIds &,bool)600 void QgsProcessingMapLayerComboBox::selectionChanged( const QgsFeatureIds &selected, const QgsFeatureIds &, bool )
601 {
602 if ( selected.isEmpty() )
603 mUseSelectionCheckBox->setChecked( false );
604 mUseSelectionCheckBox->setEnabled( !selected.isEmpty() );
605 }
606
showSourceOptions()607 void QgsProcessingMapLayerComboBox::showSourceOptions()
608 {
609 if ( QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ) )
610 {
611 QgsProcessingFeatureSourceOptionsWidget *widget = new QgsProcessingFeatureSourceOptionsWidget();
612 widget->setPanelTitle( tr( "%1 Options" ).arg( mParameter->description() ) );
613
614 widget->setGeometryCheckMethod( mIsOverridingDefaultGeometryCheck, mGeometryCheck );
615 widget->setFeatureLimit( mFeatureLimit );
616
617 panel->openPanel( widget );
618
619 connect( widget, &QgsPanelWidget::widgetChanged, this, [ = ]
620 {
621 bool changed = false;
622 changed = changed | ( widget->featureLimit() != mFeatureLimit );
623 changed = changed | ( widget->isOverridingInvalidGeometryCheck() != mIsOverridingDefaultGeometryCheck );
624 changed = changed | ( widget->geometryCheckMethod() != mGeometryCheck );
625
626 mFeatureLimit = widget->featureLimit();
627 mIsOverridingDefaultGeometryCheck = widget->isOverridingInvalidGeometryCheck();
628 mGeometryCheck = widget->geometryCheckMethod();
629
630 if ( changed )
631 emit valueChanged();
632 } );
633 }
634 }
635
selectFromFile()636 void QgsProcessingMapLayerComboBox::selectFromFile()
637 {
638 QgsSettings settings;
639 const QString initialValue = currentText();
640 QString path;
641
642 if ( QFileInfo( initialValue ).isDir() && QFileInfo::exists( initialValue ) )
643 path = initialValue;
644 else if ( QFileInfo::exists( QFileInfo( initialValue ).path() ) && QFileInfo( initialValue ).path() != '.' )
645 path = QFileInfo( initialValue ).path();
646 else if ( settings.contains( QStringLiteral( "/Processing/LastInputPath" ) ) )
647 path = settings.value( QStringLiteral( "/Processing/LastInputPath" ) ).toString();
648
649 QString filter;
650 if ( const QgsFileFilterGenerator *generator = dynamic_cast< const QgsFileFilterGenerator * >( mParameter.get() ) )
651 filter = generator->createFileFilter();
652 else
653 filter = QObject::tr( "All files (*.*)" );
654
655 const QString filename = QFileDialog::getOpenFileName( this, tr( "Select File" ), path, filter );
656 if ( filename.isEmpty() )
657 return;
658
659 settings.setValue( QStringLiteral( "/Processing/LastInputPath" ), QFileInfo( filename ).path() );
660 QgsProcessingContext context;
661 setValue( filename, context );
662 }
663
browseForLayer()664 void QgsProcessingMapLayerComboBox::browseForLayer()
665 {
666 if ( QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ) )
667 {
668 QgsDataSourceSelectWidget *widget = new QgsDataSourceSelectWidget( mBrowserModel, true, QgsMapLayerType::VectorLayer );
669 widget->setPanelTitle( tr( "Browse for \"%1\"" ).arg( mParameter->description() ) );
670
671 panel->openPanel( widget );
672
673 connect( widget, &QgsDataSourceSelectWidget::itemTriggered, this, [ = ]( const QgsMimeDataUtils::Uri & )
674 {
675 widget->acceptPanel();
676 } );
677 connect( widget, &QgsPanelWidget::panelAccepted, this, [ = ]()
678 {
679 QgsProcessingContext context;
680 if ( widget->uri().uri.isEmpty() )
681 setValue( QVariant(), context );
682 else if ( widget->uri().providerKey == QLatin1String( "ogr" ) )
683 setValue( widget->uri().uri, context );
684 else
685 setValue( QgsProcessingUtils::encodeProviderKeyAndUri( widget->uri().providerKey, widget->uri().uri ), context );
686 } );
687 }
688 }
689
690
691
692 ///@endcond
693