QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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( QgsProcessingParameterDefinition *parameter, QWidget *parent )
32  : QWidget( parent )
33  , mParameter( parameter )
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 )->dataTypes();
72  else if ( mParameter->type() == QgsProcessingParameterVectorLayer::typeName() )
73  dataTypes = static_cast< QgsProcessingParameterVectorLayer *>( mParameter )->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 
122 void QgsProcessingMapLayerComboBox::setLayer( QgsMapLayer *layer )
123 {
124  if ( layer || mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
125  mCombo->setLayer( layer );
126 }
127 
128 QgsMapLayer *QgsProcessingMapLayerComboBox::currentLayer()
129 {
130  return mCombo->currentLayer();
131 }
132 
133 QString QgsProcessingMapLayerComboBox::currentText()
134 {
135  return mCombo->currentText();
136 }
137 
138 void QgsProcessingMapLayerComboBox::setValue( const QVariant &value, QgsProcessingContext &context )
139 {
140  QVariant val = value;
141  bool found = false;
142  bool selectedOnly = false;
143  if ( val.canConvert<QgsProcessingFeatureSourceDefinition>() )
144  {
146  val = fromVar.source;
147  selectedOnly = fromVar.selectedFeaturesOnly;
148  }
149 
150  if ( val.canConvert<QgsProperty>() )
151  {
152  if ( val.value< QgsProperty >().propertyType() == QgsProperty::StaticProperty )
153  {
154  val = val.value< QgsProperty >().staticValue();
155  }
156  else
157  {
158  val = val.value< QgsProperty >().valueAsString( context.expressionContext(), mParameter->defaultValue().toString() );
159  }
160  }
161 
162  QgsMapLayer *layer = qobject_cast< QgsMapLayer * >( val.value< QObject * >() );
163  if ( !layer && val.type() == QVariant::String )
164  {
165  layer = QgsProcessingUtils::mapLayerFromString( val.toString(), context, false );
166  }
167 
168  if ( layer )
169  {
170  mBlockChangedSignal++;
171  QgsMapLayer *prevLayer = currentLayer();
172  setLayer( layer );
173  found = static_cast< bool >( currentLayer() );
174  bool changed = found && ( currentLayer() != prevLayer );
175  if ( found && mUseSelectionCheckBox )
176  {
177  const bool hasSelection = qobject_cast< QgsVectorLayer * >( layer ) && qobject_cast< QgsVectorLayer * >( layer )->selectedFeatureCount() > 0;
178  changed = changed | ( ( hasSelection && selectedOnly ) != mUseSelectionCheckBox->isChecked() );
179  if ( hasSelection )
180  {
181  mUseSelectionCheckBox->setEnabled( true );
182  mUseSelectionCheckBox->setChecked( selectedOnly );
183  }
184  else
185  {
186  mUseSelectionCheckBox->setChecked( false );
187  mUseSelectionCheckBox->setEnabled( false );
188  }
189  }
190  mBlockChangedSignal--;
191  if ( changed )
192  emit valueChanged(); // and ensure we only ever raise one
193  }
194 
195  if ( !found )
196  {
197  const QString string = val.toString();
198  if ( !string.isEmpty() )
199  {
200  mBlockChangedSignal++;
201  if ( mCombo->findText( string ) < 0 )
202  {
203  QStringList additional = mCombo->additionalItems();
204  additional.append( string );
205  mCombo->setAdditionalItems( additional );
206  }
207  mCombo->setCurrentIndex( mCombo->findText( string ) ); // this may or may not throw a signal, so let's block it..
208  mBlockChangedSignal--;
209  if ( !mBlockChangedSignal )
210  emit valueChanged(); // and ensure we only ever raise one
211  }
212  else if ( mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
213  {
214  mCombo->setLayer( nullptr );
215  }
216  }
217 }
218 
219 QVariant QgsProcessingMapLayerComboBox::value() const
220 {
221  if ( QgsMapLayer *layer = mCombo->currentLayer() )
222  {
223  if ( mUseSelectionCheckBox && mUseSelectionCheckBox->isChecked() )
224  return QgsProcessingFeatureSourceDefinition( layer->id(), true );
225  else
226  return layer->id();
227  }
228  else
229  {
230  if ( !mCombo->currentText().isEmpty() )
231  {
232  if ( mUseSelectionCheckBox && mUseSelectionCheckBox->isChecked() )
233  return QgsProcessingFeatureSourceDefinition( mCombo->currentText(), true );
234  else
235  return mCombo->currentText();
236  }
237  }
238  return QVariant();
239 }
240 
241 
242 QgsMapLayer *QgsProcessingMapLayerComboBox::compatibleMapLayerFromMimeData( const QMimeData *data, bool &incompatibleLayerSelected ) const
243 {
244  incompatibleLayerSelected = false;
246  for ( const QgsMimeDataUtils::Uri &u : uriList )
247  {
248  // is this uri from the current project?
249  if ( QgsMapLayer *layer = u.mapLayer() )
250  {
251  if ( mCombo->mProxyModel->acceptsLayer( layer ) )
252  return layer;
253  else
254  {
255  incompatibleLayerSelected = true;
256  return nullptr;
257  }
258  }
259  }
260  return nullptr;
261 }
262 
263 
264 QString QgsProcessingMapLayerComboBox::compatibleUriFromMimeData( const QMimeData *data ) const
265 {
267  for ( const QgsMimeDataUtils::Uri &u : uriList )
268  {
269  if ( ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName()
270  || mParameter->type() == QgsProcessingParameterVectorLayer::typeName()
271  || mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
272  && u.layerType == QLatin1String( "vector" ) && u.providerKey == QLatin1String( "ogr" ) )
273  {
274  QList< int > dataTypes = mParameter->type() == QgsProcessingParameterFeatureSource::typeName() ? static_cast< QgsProcessingParameterFeatureSource * >( mParameter )->dataTypes()
275  : ( mParameter->type() == QgsProcessingParameterVectorLayer::typeName() ? static_cast<QgsProcessingParameterVectorLayer *>( mParameter )->dataTypes()
276  : QList< int >() );
277  switch ( QgsWkbTypes::geometryType( u.wkbType ) )
278  {
280  return u.uri;
281 
283  if ( dataTypes.isEmpty() || dataTypes.contains( QgsProcessing::TypeVector ) || dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.contains( QgsProcessing::TypeVectorPoint ) )
284  return u.uri;
285  break;
286 
288  if ( dataTypes.isEmpty() || dataTypes.contains( QgsProcessing::TypeVector ) || dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.contains( QgsProcessing::TypeVectorLine ) )
289  return u.uri;
290  break;
291 
293  if ( dataTypes.isEmpty() || dataTypes.contains( QgsProcessing::TypeVector ) || dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.contains( QgsProcessing::TypeVectorPolygon ) )
294  return u.uri;
295  break;
296 
298  if ( dataTypes.contains( QgsProcessing::TypeVector ) )
299  return u.uri;
300  break;
301  }
302  }
303  else if ( ( mParameter->type() == QgsProcessingParameterRasterLayer::typeName()
304  || mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
305  && u.layerType == QLatin1String( "raster" ) && u.providerKey == QLatin1String( "gdal" ) )
306  return u.uri;
307  else if ( ( mParameter->type() == QgsProcessingParameterMeshLayer::typeName()
308  || mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
309  && u.layerType == QLatin1String( "mesh" ) && u.providerKey == QLatin1String( "mdal" ) )
310  return u.uri;
311  }
312  if ( !uriList.isEmpty() )
313  return QString();
314 
315  // second chance -- files dragged from file explorer, outside of QGIS
316  QStringList rawPaths;
317  if ( data->hasUrls() )
318  {
319  const QList< QUrl > urls = data->urls();
320  rawPaths.reserve( urls.count() );
321  for ( const QUrl &url : urls )
322  {
323  const QString local = url.toLocalFile();
324  if ( !rawPaths.contains( local ) )
325  rawPaths.append( local );
326  }
327  }
328  if ( !data->text().isEmpty() && !rawPaths.contains( data->text() ) )
329  rawPaths.append( data->text() );
330 
331  for ( const QString &path : qgis::as_const( rawPaths ) )
332  {
333  QFileInfo file( path );
334  if ( file.isFile() )
335  {
336  // TODO - we should check to see if it's a valid extension for the parameter, but that's non-trivial
337  return path;
338  }
339  }
340 
341  return QString();
342 }
343 
344 void QgsProcessingMapLayerComboBox::dragEnterEvent( QDragEnterEvent *event )
345 {
346  if ( !( event->possibleActions() & Qt::CopyAction ) )
347  return;
348 
349  bool incompatibleLayerSelected = false;
350  QgsMapLayer *layer = compatibleMapLayerFromMimeData( event->mimeData(), incompatibleLayerSelected );
351  const QString uri = compatibleUriFromMimeData( event->mimeData() );
352  if ( layer || ( !incompatibleLayerSelected && !uri.isEmpty() ) )
353  {
354  // dragged an acceptable layer, phew
355  event->setDropAction( Qt::CopyAction );
356  event->accept();
357  mDragActive = true;
358  mCombo->mHighlight = true;
359  update();
360  }
361 }
362 
363 void QgsProcessingMapLayerComboBox::dragLeaveEvent( QDragLeaveEvent *event )
364 {
365  QWidget::dragLeaveEvent( event );
366  if ( mDragActive )
367  {
368  event->accept();
369  mDragActive = false;
370  mCombo->mHighlight = false;
371  update();
372  }
373 }
374 
375 void QgsProcessingMapLayerComboBox::dropEvent( QDropEvent *event )
376 {
377  if ( !( event->possibleActions() & Qt::CopyAction ) )
378  return;
379 
380  bool incompatibleLayerSelected = false;
381  QgsMapLayer *layer = compatibleMapLayerFromMimeData( event->mimeData(), incompatibleLayerSelected );
382  const QString uri = compatibleUriFromMimeData( event->mimeData() );
383  if ( layer || ( !incompatibleLayerSelected && !uri.isEmpty() ) )
384  {
385  // dropped an acceptable layer, phew
386  setFocus( Qt::MouseFocusReason );
387  event->setDropAction( Qt::CopyAction );
388  event->accept();
389  QgsProcessingContext context;
390  setValue( layer ? QVariant::fromValue( layer ) : QVariant::fromValue( uri ), context );
391  }
392  mDragActive = false;
393  mCombo->mHighlight = false;
394  update();
395 }
396 
397 void QgsProcessingMapLayerComboBox::onLayerChanged( QgsMapLayer *layer )
398 {
399  if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() )
400  {
401  if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
402  {
403  if ( QgsVectorLayer *prevLayer = qobject_cast< QgsVectorLayer * >( mPrevLayer ) )
404  {
405  disconnect( prevLayer, &QgsVectorLayer::selectionChanged, this, &QgsProcessingMapLayerComboBox::selectionChanged );
406  }
407  if ( vl->selectedFeatureCount() == 0 )
408  mUseSelectionCheckBox->setChecked( false );
409  mUseSelectionCheckBox->setEnabled( vl->selectedFeatureCount() > 0 );
410  connect( vl, &QgsVectorLayer::selectionChanged, this, &QgsProcessingMapLayerComboBox::selectionChanged );
411  }
412  }
413 
414  mPrevLayer = layer;
415  if ( !mBlockChangedSignal )
416  emit valueChanged();
417 }
418 
419 void QgsProcessingMapLayerComboBox::selectionChanged( const QgsFeatureIds &selected, const QgsFeatureIds &, bool )
420 {
421  if ( selected.isEmpty() )
422  mUseSelectionCheckBox->setChecked( false );
423  mUseSelectionCheckBox->setEnabled( !selected.isEmpty() );
424 }
425 
426 
427 
Base class for all map layer types.
Definition: qgsmaplayer.h:78
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.
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:666
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