QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
qgsprocessingmaplayercombobox.cpp
Go to the documentation of this file.
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 
17 #include "qgsmaplayercombobox.h"
18 #include "qgsmimedatautils.h"
20 #include "qgssettings.h"
21 #include "qgsvectorlayer.h"
22 #include "qgsfeatureid.h"
23 #include <QHBoxLayout>
24 #include <QVBoxLayout>
25 #include <QToolButton>
26 #include <QCheckBox>
27 #include <QDragEnterEvent>
28 
30 
31 QgsProcessingMapLayerComboBox::QgsProcessingMapLayerComboBox( const QgsProcessingParameterDefinition *parameter, QWidget *parent )
32  : QWidget( parent )
33  , mParameter( parameter->clone() )
34 {
35  QHBoxLayout *layout = new QHBoxLayout();
36  layout->setMargin( 0 );
37  layout->setContentsMargins( 0, 0, 0, 0 );
38  layout->setSpacing( 6 );
39 
40  mCombo = new QgsMapLayerComboBox();
41  layout->addWidget( mCombo );
42  layout->setAlignment( mCombo, Qt::AlignTop );
43 
44  mSelectButton = new QToolButton();
45  mSelectButton->setText( QStringLiteral( "…" ) );
46  mSelectButton->setToolTip( tr( "Select file" ) );
47  connect( mSelectButton, &QToolButton::clicked, this, &QgsProcessingMapLayerComboBox::triggerFileSelection );
48  layout->addWidget( mSelectButton );
49  layout->setAlignment( mSelectButton, Qt::AlignTop );
50 
51  QVBoxLayout *vl = new QVBoxLayout();
52  vl->setMargin( 0 );
53  vl->setContentsMargins( 0, 0, 0, 0 );
54  vl->setSpacing( 6 );
55  vl->addLayout( layout );
56 
57  QgsMapLayerProxyModel::Filters filters = nullptr;
58 
59  if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() )
60  {
61  mUseSelectionCheckBox = new QCheckBox( tr( "Selected features only" ) );
62  mUseSelectionCheckBox->setChecked( false );
63  mUseSelectionCheckBox->setEnabled( false );
64  vl->addWidget( mUseSelectionCheckBox );
65  }
66 
67  if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() || mParameter->type() == QgsProcessingParameterVectorLayer::typeName() )
68  {
69  QList<int> dataTypes;
70  if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() )
71  dataTypes = static_cast< QgsProcessingParameterFeatureSource *>( mParameter.get() )->dataTypes();
72  else if ( mParameter->type() == QgsProcessingParameterVectorLayer::typeName() )
73  dataTypes = static_cast< QgsProcessingParameterVectorLayer *>( mParameter.get() )->dataTypes();
74 
75  if ( dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.isEmpty() )
77  if ( dataTypes.contains( QgsProcessing::TypeVectorPoint ) )
79  if ( dataTypes.contains( QgsProcessing::TypeVectorLine ) )
81  if ( dataTypes.contains( QgsProcessing::TypeVectorPolygon ) )
83  if ( !filters )
85  }
86  else if ( mParameter->type() == QgsProcessingParameterRasterLayer::typeName() )
87  {
89  }
90  else if ( mParameter->type() == QgsProcessingParameterMeshLayer::typeName() )
91  {
93  }
94 
95  QgsSettings settings;
96  if ( settings.value( QStringLiteral( "Processing/Configuration/SHOW_CRS_DEF" ), true ).toBool() )
97  mCombo->setShowCrs( true );
98 
99  if ( filters )
100  mCombo->setFilters( filters );
101  mCombo->setExcludedProviders( QStringList() << QStringLiteral( "grass" ) ); // not sure if this is still required...
102 
103  if ( mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
104  {
105  mCombo->setAllowEmptyLayer( true );
106  mCombo->setLayer( nullptr );
107  }
108 
109  connect( mCombo, &QgsMapLayerComboBox::layerChanged, this, &QgsProcessingMapLayerComboBox::onLayerChanged );
110  if ( mUseSelectionCheckBox )
111  connect( mUseSelectionCheckBox, &QCheckBox::toggled, this, [ = ]
112  {
113  if ( !mBlockChangedSignal )
114  emit valueChanged();
115  } );
116 
117  setLayout( vl );
118 
119  setAcceptDrops( true );
120 
121  onLayerChanged( mCombo->currentLayer() );
122 }
123 
124 QgsProcessingMapLayerComboBox::~QgsProcessingMapLayerComboBox() = default;
125 
126 void QgsProcessingMapLayerComboBox::setLayer( QgsMapLayer *layer )
127 {
128  if ( layer || mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
129  mCombo->setLayer( layer );
130 }
131 
132 QgsMapLayer *QgsProcessingMapLayerComboBox::currentLayer()
133 {
134  return mCombo->currentLayer();
135 }
136 
137 QString QgsProcessingMapLayerComboBox::currentText()
138 {
139  return mCombo->currentText();
140 }
141 
142 void QgsProcessingMapLayerComboBox::setValue( const QVariant &value, QgsProcessingContext &context )
143 {
144  QVariant val = value;
145  bool found = false;
146  bool selectedOnly = false;
147  if ( val.canConvert<QgsProcessingFeatureSourceDefinition>() )
148  {
150  val = fromVar.source;
151  selectedOnly = fromVar.selectedFeaturesOnly;
152  }
153 
154  if ( val.canConvert<QgsProperty>() )
155  {
156  if ( val.value< QgsProperty >().propertyType() == QgsProperty::StaticProperty )
157  {
158  val = val.value< QgsProperty >().staticValue();
159  }
160  else
161  {
162  val = val.value< QgsProperty >().valueAsString( context.expressionContext(), mParameter->defaultValue().toString() );
163  }
164  }
165 
166  QgsMapLayer *layer = qobject_cast< QgsMapLayer * >( val.value< QObject * >() );
167  if ( !layer && val.type() == QVariant::String )
168  {
169  layer = QgsProcessingUtils::mapLayerFromString( val.toString(), context, false );
170  }
171 
172  if ( layer )
173  {
174  mBlockChangedSignal++;
175  QgsMapLayer *prevLayer = currentLayer();
176  setLayer( layer );
177  found = static_cast< bool >( currentLayer() );
178  bool changed = found && ( currentLayer() != prevLayer );
179  if ( found && mUseSelectionCheckBox )
180  {
181  const bool hasSelection = qobject_cast< QgsVectorLayer * >( layer ) && qobject_cast< QgsVectorLayer * >( layer )->selectedFeatureCount() > 0;
182  changed = changed | ( ( hasSelection && selectedOnly ) != mUseSelectionCheckBox->isChecked() );
183  if ( hasSelection )
184  {
185  mUseSelectionCheckBox->setEnabled( true );
186  mUseSelectionCheckBox->setChecked( selectedOnly );
187  }
188  else
189  {
190  mUseSelectionCheckBox->setChecked( false );
191  mUseSelectionCheckBox->setEnabled( false );
192  }
193  }
194  mBlockChangedSignal--;
195  if ( changed )
196  emit valueChanged(); // and ensure we only ever raise one
197  }
198 
199  if ( !found )
200  {
201  const QString string = val.toString();
202  if ( mUseSelectionCheckBox )
203  {
204  mUseSelectionCheckBox->setChecked( false );
205  mUseSelectionCheckBox->setEnabled( false );
206  }
207  if ( !string.isEmpty() )
208  {
209  mBlockChangedSignal++;
210  if ( mCombo->findText( string ) < 0 )
211  {
212  QStringList additional = mCombo->additionalItems();
213  additional.append( string );
214  mCombo->setAdditionalItems( additional );
215  }
216  mCombo->setCurrentIndex( mCombo->findText( string ) ); // this may or may not throw a signal, so let's block it..
217  mBlockChangedSignal--;
218  if ( !mBlockChangedSignal )
219  emit valueChanged(); // and ensure we only ever raise one
220  }
221  else if ( mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
222  {
223  mCombo->setLayer( nullptr );
224  }
225  }
226 }
227 
228 QVariant QgsProcessingMapLayerComboBox::value() const
229 {
230  if ( QgsMapLayer *layer = mCombo->currentLayer() )
231  {
232  if ( mUseSelectionCheckBox && mUseSelectionCheckBox->isChecked() )
233  return QgsProcessingFeatureSourceDefinition( layer->id(), true );
234  else
235  return layer->id();
236  }
237  else
238  {
239  if ( !mCombo->currentText().isEmpty() )
240  {
241  if ( mUseSelectionCheckBox && mUseSelectionCheckBox->isChecked() )
242  return QgsProcessingFeatureSourceDefinition( mCombo->currentText(), true );
243  else
244  return mCombo->currentText();
245  }
246  }
247  return QVariant();
248 }
249 
250 
251 QgsMapLayer *QgsProcessingMapLayerComboBox::compatibleMapLayerFromMimeData( const QMimeData *data, bool &incompatibleLayerSelected ) const
252 {
253  incompatibleLayerSelected = false;
255  for ( const QgsMimeDataUtils::Uri &u : uriList )
256  {
257  // is this uri from the current project?
258  if ( QgsMapLayer *layer = u.mapLayer() )
259  {
260  if ( mCombo->mProxyModel->acceptsLayer( layer ) )
261  return layer;
262  else
263  {
264  incompatibleLayerSelected = true;
265  return nullptr;
266  }
267  }
268  }
269  return nullptr;
270 }
271 
272 
273 QString QgsProcessingMapLayerComboBox::compatibleUriFromMimeData( const QMimeData *data ) const
274 {
276  for ( const QgsMimeDataUtils::Uri &u : uriList )
277  {
278  if ( ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName()
279  || mParameter->type() == QgsProcessingParameterVectorLayer::typeName()
280  || mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
281  && u.layerType == QLatin1String( "vector" ) && u.providerKey == QLatin1String( "ogr" ) )
282  {
283  QList< int > dataTypes = mParameter->type() == QgsProcessingParameterFeatureSource::typeName() ? static_cast< QgsProcessingParameterFeatureSource * >( mParameter.get() )->dataTypes()
284  : ( mParameter->type() == QgsProcessingParameterVectorLayer::typeName() ? static_cast<QgsProcessingParameterVectorLayer *>( mParameter.get() )->dataTypes()
285  : QList< int >() );
286  switch ( QgsWkbTypes::geometryType( u.wkbType ) )
287  {
289  return u.uri;
290 
292  if ( dataTypes.isEmpty() || dataTypes.contains( QgsProcessing::TypeVector ) || dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.contains( QgsProcessing::TypeVectorPoint ) )
293  return u.uri;
294  break;
295 
297  if ( dataTypes.isEmpty() || dataTypes.contains( QgsProcessing::TypeVector ) || dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.contains( QgsProcessing::TypeVectorLine ) )
298  return u.uri;
299  break;
300 
302  if ( dataTypes.isEmpty() || dataTypes.contains( QgsProcessing::TypeVector ) || dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.contains( QgsProcessing::TypeVectorPolygon ) )
303  return u.uri;
304  break;
305 
307  if ( dataTypes.contains( QgsProcessing::TypeVector ) )
308  return u.uri;
309  break;
310  }
311  }
312  else if ( ( mParameter->type() == QgsProcessingParameterRasterLayer::typeName()
313  || mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
314  && u.layerType == QLatin1String( "raster" ) && u.providerKey == QLatin1String( "gdal" ) )
315  return u.uri;
316  else if ( ( mParameter->type() == QgsProcessingParameterMeshLayer::typeName()
317  || mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
318  && u.layerType == QLatin1String( "mesh" ) && u.providerKey == QLatin1String( "mdal" ) )
319  return u.uri;
320  }
321  if ( !uriList.isEmpty() )
322  return QString();
323 
324  // second chance -- files dragged from file explorer, outside of QGIS
325  QStringList rawPaths;
326  if ( data->hasUrls() )
327  {
328  const QList< QUrl > urls = data->urls();
329  rawPaths.reserve( urls.count() );
330  for ( const QUrl &url : urls )
331  {
332  const QString local = url.toLocalFile();
333  if ( !rawPaths.contains( local ) )
334  rawPaths.append( local );
335  }
336  }
337  if ( !data->text().isEmpty() && !rawPaths.contains( data->text() ) )
338  rawPaths.append( data->text() );
339 
340  for ( const QString &path : qgis::as_const( rawPaths ) )
341  {
342  QFileInfo file( path );
343  if ( file.isFile() )
344  {
345  // TODO - we should check to see if it's a valid extension for the parameter, but that's non-trivial
346  return path;
347  }
348  }
349 
350  return QString();
351 }
352 
353 void QgsProcessingMapLayerComboBox::dragEnterEvent( QDragEnterEvent *event )
354 {
355  if ( !( event->possibleActions() & Qt::CopyAction ) )
356  return;
357 
358  bool incompatibleLayerSelected = false;
359  QgsMapLayer *layer = compatibleMapLayerFromMimeData( event->mimeData(), incompatibleLayerSelected );
360  const QString uri = compatibleUriFromMimeData( event->mimeData() );
361  if ( layer || ( !incompatibleLayerSelected && !uri.isEmpty() ) )
362  {
363  // dragged an acceptable layer, phew
364  event->setDropAction( Qt::CopyAction );
365  event->accept();
366  mDragActive = true;
367  mCombo->mHighlight = true;
368  update();
369  }
370 }
371 
372 void QgsProcessingMapLayerComboBox::dragLeaveEvent( QDragLeaveEvent *event )
373 {
374  QWidget::dragLeaveEvent( event );
375  if ( mDragActive )
376  {
377  event->accept();
378  mDragActive = false;
379  mCombo->mHighlight = false;
380  update();
381  }
382 }
383 
384 void QgsProcessingMapLayerComboBox::dropEvent( QDropEvent *event )
385 {
386  if ( !( event->possibleActions() & Qt::CopyAction ) )
387  return;
388 
389  bool incompatibleLayerSelected = false;
390  QgsMapLayer *layer = compatibleMapLayerFromMimeData( event->mimeData(), incompatibleLayerSelected );
391  const QString uri = compatibleUriFromMimeData( event->mimeData() );
392  if ( layer || ( !incompatibleLayerSelected && !uri.isEmpty() ) )
393  {
394  // dropped an acceptable layer, phew
395  setFocus( Qt::MouseFocusReason );
396  event->setDropAction( Qt::CopyAction );
397  event->accept();
398  QgsProcessingContext context;
399  setValue( layer ? QVariant::fromValue( layer ) : QVariant::fromValue( uri ), context );
400  }
401  mDragActive = false;
402  mCombo->mHighlight = false;
403  update();
404 }
405 
406 void QgsProcessingMapLayerComboBox::onLayerChanged( QgsMapLayer *layer )
407 {
408  if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() )
409  {
410  if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
411  {
412  if ( QgsVectorLayer *prevLayer = qobject_cast< QgsVectorLayer * >( mPrevLayer ) )
413  {
414  disconnect( prevLayer, &QgsVectorLayer::selectionChanged, this, &QgsProcessingMapLayerComboBox::selectionChanged );
415  }
416  if ( vl->selectedFeatureCount() == 0 )
417  mUseSelectionCheckBox->setChecked( false );
418  mUseSelectionCheckBox->setEnabled( vl->selectedFeatureCount() > 0 );
419  connect( vl, &QgsVectorLayer::selectionChanged, this, &QgsProcessingMapLayerComboBox::selectionChanged );
420  }
421  }
422 
423  mPrevLayer = layer;
424  if ( !mBlockChangedSignal )
425  emit valueChanged();
426 }
427 
428 void QgsProcessingMapLayerComboBox::selectionChanged( const QgsFeatureIds &selected, const QgsFeatureIds &, bool )
429 {
430  if ( selected.isEmpty() )
431  mUseSelectionCheckBox->setChecked( false );
432  mUseSelectionCheckBox->setEnabled( !selected.isEmpty() );
433 }
434 
435 
436 
Base class for all map layer types.
Definition: qgsmaplayer.h:79
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:34
QgsMapLayerType type() const
Returns the type of the layer.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
static QString typeName()
Returns the type name for the parameter class.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
static QString typeName()
Returns the type name for the parameter class.
static UriList decodeUriList(const QMimeData *data)
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
static QString typeName()
Returns the type name for the parameter class.
virtual bool accept(QgsStyleEntityVisitorInterface *visitor) const
Accepts the specified symbology visitor, causing it to visit all symbols associated with the layer...
QgsMapLayer::LayerFlags flags() const
Returns the flags for this layer.
void layerChanged(QgsMapLayer *layer)
Emitted whenever the currently selected layer changes.
bool selectedFeaturesOnly
true if only selected features in the source should be used by algorithms.
static QString typeName()
Returns the type name for the parameter class.
static QgsMapLayer * mapLayerFromString(const QString &string, QgsProcessingContext &context, bool allowLoadingNewLayers=true, QgsProcessingUtils::LayerHint typeHint=QgsProcessingUtils::LayerHint::UnknownType)
Interprets a string as a map layer within the supplied context.
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
QgsProperty source
Source definition.
Type propertyType() const
Returns the property type.
static QString typeName()
Returns the type name for the parameter class.
static GeometryType geometryType(Type type)
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:812
The QgsMapLayerComboBox class is a combo box which displays the list of layers.
Vector polygon layers.
Definition: qgsprocessing.h:50
A vector layer (with or without geometry) parameter for processing algorithms.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
A store for object properties.
Definition: qgsproperty.h:229
QgsExpressionContext & expressionContext()
Returns the expression context.
Encapsulates settings relating to a feature source input to a processing algorithm.
Vector point layers.
Definition: qgsprocessing.h:48
An input feature source (such as vector layers) parameter for processing algorithms.
Base class for the definition of processing parameters.
Vector line layers.
Definition: qgsprocessing.h:49
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:53
QList< QgsMimeDataUtils::Uri > UriList
Represents a vector layer which manages a vector based data sets.
Static property (QgsStaticProperty)
Definition: qgsproperty.h:237
Contains information about the context in which a processing algorithm is executed.
Any vector layer with geometry.
Definition: qgsprocessing.h:47