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