[processing] Port map layer selection combobox widget to c++

And:

- fix enable state of selected features only after changing between
map layers with/without selections
- fix state of selected features only when running an algorithm
from the history list, e.g. respect original setting for
selected features only (or not)
- ensure no duplicate changed signals are sent, and correctly
emit changed signals in all applicable circumstances
- handle drag and dropped layers from browser panel (UX fix)
- soak with unit tests
This commit is contained in:
Nyall Dawson 2019-06-13 16:56:29 +10:00
parent 9d82273208
commit fbd243be65
10 changed files with 1110 additions and 234 deletions

View File

@ -0,0 +1,111 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/processing/qgsprocessingmaplayercombobox.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsProcessingMapLayerComboBox : QWidget
{
%Docstring
Processing map layer combo box.
.. warning::
Not part of stable API and may change in future QGIS releases.
.. versionadded:: 3.8
%End
%TypeHeaderCode
#include "qgsprocessingmaplayercombobox.h"
%End
public:
QgsProcessingMapLayerComboBox( QgsProcessingParameterDefinition *parameter, QWidget *parent = 0 );
%Docstring
Constructor for QgsProcessingMapLayerComboBox, with the specified ``parameter`` definition.
%End
void setLayer( QgsMapLayer *layer );
%Docstring
Sets the combo box to the specified ``layer``, if ``layer`` is compatible with the
widget's parameter definition.
%End
QgsMapLayer *currentLayer();
%Docstring
Returns the current layer selected in the combobox, or ``None`` if the selection cannot
be represented as a map layer.
.. warning::
Prefer calling value() instead, as it correctly encapsulates all valid
values which can be represented by the widget.
.. seealso:: :py:func:`currentText`
%End
QString currentText();
%Docstring
Returns the current text of the selected item in the combobox.
.. warning::
Prefer calling value() instead, as it correctly encapsulates all valid
values which can be represented by the widget.
.. seealso:: :py:func:`currentLayer`
%End
void setValue( const QVariant &value, QgsProcessingContext &context );
%Docstring
Sets the ``value`` shown in the widget.
.. seealso:: :py:func:`value`
%End
QVariant value() const;
%Docstring
Returns the current value of the widget.
.. seealso:: :py:func:`setValue`
%End
signals:
void valueChanged();
%Docstring
Emitted whenever the value is changed in the widget.
%End
void triggerFileSelection();
%Docstring
Emitted when the widget has triggered a file selection operation (to be
handled in Python for now).
%End
protected:
virtual void dragEnterEvent( QDragEnterEvent *event );
virtual void dragLeaveEvent( QDragLeaveEvent *event );
virtual void dropEvent( QDropEvent *event );
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/processing/qgsprocessingmaplayercombobox.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -330,6 +330,7 @@
%Include auto_generated/locator/qgslocatorwidget.sip
%Include auto_generated/processing/qgsprocessingalgorithmconfigurationwidget.sip
%Include auto_generated/processing/qgsprocessingalgorithmdialogbase.sip
%Include auto_generated/processing/qgsprocessingmaplayercombobox.sip
%Include auto_generated/processing/qgsprocessingmodelerparameterwidget.sip
%Include auto_generated/processing/qgsprocessingmultipleselectiondialog.sip
%Include auto_generated/processing/qgsprocessingrecentalgorithmlog.sip

View File

@ -104,7 +104,8 @@ from qgis.gui import (
QgsProjectionSelectionWidget,
QgsRasterBandComboBox,
QgsProcessingGui,
QgsAbstractProcessingParameterWidgetWrapper
QgsAbstractProcessingParameterWidgetWrapper,
QgsProcessingMapLayerComboBox
)
from qgis.PyQt.QtCore import pyqtSignal, QObject, QVariant, Qt
from qgis.utils import iface
@ -931,37 +932,17 @@ class MapLayerWidgetWrapper(WidgetWrapper):
def createWidget(self):
if self.dialogType == DIALOG_STANDARD:
widget = QWidget()
layout = QHBoxLayout()
layout.setMargin(0)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(6)
self.combo = QgsMapLayerComboBox()
layout.addWidget(self.combo)
btn = QToolButton()
btn.setText('')
btn.setToolTip(self.tr("Select file"))
btn.clicked.connect(self.selectFile)
layout.addWidget(btn)
widget.setLayout(layout)
if ProcessingConfig.getSetting(ProcessingConfig.SHOW_CRS_DEF):
self.combo.setShowCrs(True)
self.setComboBoxFilters(self.combo)
self.combo = QgsProcessingMapLayerComboBox(self.parameterDefinition())
self.context = dataobjects.createContext()
try:
self.combo.setLayer(iface.activeLayer())
except:
pass
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
self.combo.setAllowEmptyLayer(True)
self.combo.setLayer(None)
self.combo.currentIndexChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
self.combo.currentTextChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
return widget
self.combo.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
self.combo.triggerFileSelection.connect(self.selectFile)
return self.combo
elif self.dialogType == DIALOG_BATCH:
widget = BatchInputSelectionPanel(self.parameterDefinition(), self.row, self.col, self.dialog)
widget.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
@ -989,9 +970,6 @@ class MapLayerWidgetWrapper(WidgetWrapper):
widget.setLayout(layout)
return widget
def setComboBoxFilters(self, combo):
pass
def getAvailableLayers(self):
return self.dialog.getAvailableValuesOfType(
[QgsProcessingParameterRasterLayer, QgsProcessingParameterMeshLayer, QgsProcessingParameterVectorLayer, QgsProcessingParameterMapLayer, QgsProcessingParameterString],
@ -1000,7 +978,9 @@ class MapLayerWidgetWrapper(WidgetWrapper):
def selectFile(self):
filename, selected_filter = self.getFileName(self.combo.currentText())
if filename:
if isinstance(self.combo, QgsMapLayerComboBox):
if isinstance(self.combo, QgsProcessingMapLayerComboBox):
self.combo.setValue(filename, self.context)
elif isinstance(self.combo, QgsMapLayerComboBox):
items = self.combo.additionalItems()
items.append(filename)
self.combo.setAdditionalItems(items)
@ -1018,20 +998,7 @@ class MapLayerWidgetWrapper(WidgetWrapper):
layer = QgsProject.instance().mapLayer(value)
if layer is not None:
value = layer
found = False
if isinstance(value, QgsMapLayer):
self.combo.setLayer(value)
found = self.combo.currentIndex() != -1
if not found:
if self.combo.findText(value) >= 0:
self.combo.setCurrentIndex(self.combo.findText(value))
else:
items = self.combo.additionalItems()
items.append(value)
self.combo.setAdditionalItems(items)
self.combo.setCurrentIndex(self.combo.findText(value))
self.combo.setValue(value, self.context)
elif self.dialogType == DIALOG_BATCH:
self.widget.setValue(value)
else:
@ -1040,14 +1007,7 @@ class MapLayerWidgetWrapper(WidgetWrapper):
def value(self):
if self.dialogType == DIALOG_STANDARD:
try:
layer = self.combo.currentLayer()
if layer is not None:
return layer
else:
return self.combo.currentText() or None
except:
return self.combo.currentText()
return self.combo.value()
elif self.dialogType == DIALOG_BATCH:
return self.widget.value()
else:
@ -1066,15 +1026,13 @@ class RasterWidgetWrapper(MapLayerWidgetWrapper):
return self.dialog.getAvailableValuesOfType((QgsProcessingParameterRasterLayer, QgsProcessingParameterString),
(QgsProcessingOutputRasterLayer, QgsProcessingOutputFile, QgsProcessingOutputString))
def setComboBoxFilters(self, combo):
combo.setFilters(QgsMapLayerProxyModel.RasterLayer)
combo.setExcludedProviders(['grass'])
def selectFile(self):
filename, selected_filter = self.getFileName(self.combo.currentText())
if filename:
filename = dataobjects.getRasterSublayer(filename, self.parameterDefinition())
if isinstance(self.combo, QgsMapLayerComboBox):
if isinstance(self.combo, QgsProcessingMapLayerComboBox):
self.combo.setValue(filename, self.context)
elif isinstance(self.combo, QgsMapLayerComboBox):
items = self.combo.additionalItems()
items.append(filename)
self.combo.setAdditionalItems(items)
@ -1090,13 +1048,12 @@ class MeshWidgetWrapper(MapLayerWidgetWrapper):
return self.dialog.getAvailableValuesOfType((QgsProcessingParameterMeshLayer, QgsProcessingParameterString),
())
def setComboBoxFilters(self, combo):
combo.setFilters(QgsMapLayerProxyModel.MeshLayer)
def selectFile(self):
filename, selected_filter = self.getFileName(self.combo.currentText())
if filename:
if isinstance(self.combo, QgsMapLayerComboBox):
if isinstance(self.combo, QgsProcessingMapLayerComboBox):
self.combo.setValue(filename, self.context)
elif isinstance(self.combo, QgsMapLayerComboBox):
items = self.combo.additionalItems()
items.append(filename)
self.combo.setAdditionalItems(items)
@ -1177,70 +1134,19 @@ class FeatureSourceWidgetWrapper(WidgetWrapper):
NOT_SELECTED = '[Not selected]'
def createWidget(self):
self.fileBasedLayers = {}
if self.dialogType == DIALOG_STANDARD:
widget = QWidget()
layout = QHBoxLayout()
layout.setMargin(0)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(6)
self.combo = QgsMapLayerComboBox()
layout.addWidget(self.combo)
layout.setAlignment(self.combo, Qt.AlignTop)
btn = QToolButton()
btn.setText('')
btn.setToolTip(self.tr("Select file"))
btn.clicked.connect(self.selectFile)
layout.addWidget(btn)
layout.setAlignment(btn, Qt.AlignTop)
vl = QVBoxLayout()
vl.setMargin(0)
vl.setContentsMargins(0, 0, 0, 0)
vl.setSpacing(6)
vl.addLayout(layout)
self.use_selection_checkbox = QCheckBox(self.tr('Selected features only'))
self.use_selection_checkbox.setChecked(False)
self.use_selection_checkbox.setEnabled(False)
vl.addWidget(self.use_selection_checkbox)
widget.setLayout(vl)
filters = QgsMapLayerProxyModel.Filters()
if QgsProcessing.TypeVectorAnyGeometry in self.parameterDefinition().dataTypes() or len(self.parameterDefinition().dataTypes()) == 0:
filters = QgsMapLayerProxyModel.HasGeometry
if QgsProcessing.TypeVectorPoint in self.parameterDefinition().dataTypes():
filters |= QgsMapLayerProxyModel.PointLayer
if QgsProcessing.TypeVectorLine in self.parameterDefinition().dataTypes():
filters |= QgsMapLayerProxyModel.LineLayer
if QgsProcessing.TypeVectorPolygon in self.parameterDefinition().dataTypes():
filters |= QgsMapLayerProxyModel.PolygonLayer
if not filters:
filters = QgsMapLayerProxyModel.VectorLayer
self.combo = QgsProcessingMapLayerComboBox(self.parameterDefinition())
self.context = dataobjects.createContext()
try:
if iface.activeLayer().type() == QgsMapLayerType.VectorLayer:
self.combo.setLayer(iface.activeLayer())
self.use_selection_checkbox.setEnabled(iface.activeLayer().selectedFeatureCount() > 0)
except:
pass
if ProcessingConfig.getSetting(ProcessingConfig.SHOW_CRS_DEF):
self.combo.setShowCrs(True)
if filters:
self.combo.setFilters(filters)
self.combo.setExcludedProviders(['grass'])
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
self.combo.setAllowEmptyLayer(True)
self.combo.setLayer(None)
self.combo.layerChanged.connect(self.layerChanged)
return widget
self.combo.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
self.combo.triggerFileSelection.connect(self.selectFile)
return self.combo
elif self.dialogType == DIALOG_BATCH:
widget = BatchInputSelectionPanel(self.parameterDefinition(), self.row, self.col, self.dialog)
widget.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
@ -1270,19 +1176,13 @@ class FeatureSourceWidgetWrapper(WidgetWrapper):
widget.setLayout(layout)
return widget
def layerChanged(self, layer):
if layer is None or layer.type() != QgsMapLayerType.VectorLayer or layer.selectedFeatureCount() == 0:
self.use_selection_checkbox.setChecked(False)
self.use_selection_checkbox.setEnabled(False)
else:
self.use_selection_checkbox.setEnabled(True)
self.widgetValueHasChanged.emit(self)
def selectFile(self):
filename, selected_filter = self.getFileName(self.combo.currentText())
if filename:
filename = dataobjects.getRasterSublayer(filename, self.parameterDefinition())
if isinstance(self.combo, QgsMapLayerComboBox):
if isinstance(self.combo, QgsProcessingMapLayerComboBox):
self.combo.setValue(filename, self.context)
elif isinstance(self.combo, QgsMapLayerComboBox):
items = self.combo.additionalItems()
items.append(filename)
self.combo.setAdditionalItems(items)
@ -1300,20 +1200,7 @@ class FeatureSourceWidgetWrapper(WidgetWrapper):
layer = QgsProject.instance().mapLayer(value)
if layer is not None:
value = layer
found = False
if isinstance(value, QgsMapLayer):
self.combo.setLayer(value)
found = self.combo.currentIndex() != -1
if not found:
if self.combo.findText(value) >= 0:
self.combo.setCurrentIndex(self.combo.findText(value))
else:
items = self.combo.additionalItems()
items.append(value)
self.combo.setAdditionalItems(items)
self.combo.setCurrentIndex(self.combo.findText(value))
self.combo.setValue(value, self.context)
elif self.dialogType == DIALOG_BATCH:
self.widget.setValue(value)
else:
@ -1322,24 +1209,7 @@ class FeatureSourceWidgetWrapper(WidgetWrapper):
def value(self):
if self.dialogType == DIALOG_STANDARD:
use_selected_features = self.use_selection_checkbox.isChecked()
try:
layer = self.combo.currentLayer()
if layer is not None:
if use_selected_features:
return QgsProcessingFeatureSourceDefinition(layer.id(), True)
else:
return layer.id()
else:
if self.combo.currentText():
if use_selected_features:
return QgsProcessingFeatureSourceDefinition(self.combo.currentText(), True)
else:
return self.combo.currentText()
else:
return None
except:
return QgsProcessingFeatureSourceDefinition(self.combo.currentText(), use_selected_features)
return self.combo.value()
elif self.dialogType == DIALOG_BATCH:
return self.widget.getValue()
else:
@ -1538,56 +1408,19 @@ class VectorLayerWidgetWrapper(WidgetWrapper):
NOT_SELECTED = '[Not selected]'
def createWidget(self):
self.fileBasedLayers = {}
if self.dialogType == DIALOG_STANDARD:
widget = QWidget()
layout = QHBoxLayout()
layout.setMargin(0)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(6)
self.combo = QgsMapLayerComboBox()
layout.addWidget(self.combo)
btn = QToolButton()
btn.setText('')
btn.setToolTip(self.tr("Select file"))
btn.clicked.connect(self.selectFile)
layout.addWidget(btn)
self.combo = QgsProcessingMapLayerComboBox(self.parameterDefinition())
self.context = dataobjects.createContext()
widget.setLayout(layout)
if ProcessingConfig.getSetting(ProcessingConfig.SHOW_CRS_DEF):
self.combo.setShowCrs(True)
filters = QgsMapLayerProxyModel.Filters()
if QgsProcessing.TypeVectorAnyGeometry in self.parameterDefinition().dataTypes() or len(self.parameterDefinition().dataTypes()) == 0:
filters = QgsMapLayerProxyModel.HasGeometry
if QgsProcessing.TypeVectorPoint in self.parameterDefinition().dataTypes():
filters |= QgsMapLayerProxyModel.PointLayer
if QgsProcessing.TypeVectorLine in self.parameterDefinition().dataTypes():
filters |= QgsMapLayerProxyModel.LineLayer
if QgsProcessing.TypeVectorPolygon in self.parameterDefinition().dataTypes():
filters |= QgsMapLayerProxyModel.PolygonLayer
if not filters:
filters = QgsMapLayerProxyModel.VectorLayer
if filters:
self.combo.setFilters(filters)
self.combo.setExcludedProviders(['grass'])
try:
if iface.activeLayer().type() == QgsMapLayerType.VectorLayer:
self.combo.setLayer(iface.activeLayer())
except:
pass
if self.parameterDefinition().flags() & QgsProcessingParameterDefinition.FlagOptional:
self.combo.setAllowEmptyLayer(True)
self.combo.setLayer(None)
self.combo.currentIndexChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
self.combo.currentTextChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
return widget
self.combo.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
self.combo.triggerFileSelection.connect(self.selectFile)
return self.combo
elif self.dialogType == DIALOG_BATCH:
widget = BatchInputSelectionPanel(self.parameterDefinition(), self.row, self.col, self.dialog)
widget.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
@ -1620,7 +1453,9 @@ class VectorLayerWidgetWrapper(WidgetWrapper):
filename, selected_filter = self.getFileName(self.combo.currentText())
if filename:
filename = dataobjects.getRasterSublayer(filename, self.parameterDefinition())
if isinstance(self.combo, QgsMapLayerComboBox):
if isinstance(self.combo, QgsProcessingMapLayerComboBox):
self.combo.setValue(filename, self.context)
elif isinstance(self.combo, QgsMapLayerComboBox):
items = self.combo.additionalItems()
items.append(filename)
self.combo.setAdditionalItems(items)
@ -1638,20 +1473,7 @@ class VectorLayerWidgetWrapper(WidgetWrapper):
layer = QgsProject.instance().mapLayer(value)
if layer is not None:
value = layer
found = False
if isinstance(value, QgsMapLayer):
self.combo.setLayer(value)
found = self.combo.currentIndex() != -1
if not found:
if self.combo.findText(value) >= 0:
self.combo.setCurrentIndex(self.combo.findText(value))
else:
items = self.combo.additionalItems()
items.append(value)
self.combo.setAdditionalItems(items)
self.combo.setCurrentIndex(self.combo.findText(value))
self.combo.setValue(value, self.context)
elif self.dialogType == DIALOG_BATCH:
return self.widget.setValue(value)
else:
@ -1660,14 +1482,7 @@ class VectorLayerWidgetWrapper(WidgetWrapper):
def value(self):
if self.dialogType == DIALOG_STANDARD:
try:
layer = self.combo.currentLayer()
if layer is not None:
return layer
else:
return self.combo.currentText()
except:
return self.combo.currentText()
return self.combo.value()
elif self.dialogType == DIALOG_BATCH:
return self.widget.value()
else:
@ -1686,6 +1501,7 @@ class TableFieldWidgetWrapper(WidgetWrapper):
def createWidget(self):
self._layer = None
self.parent_file_based_layers = {}
if self.dialogType in (DIALOG_STANDARD, DIALOG_BATCH):
if self.parameterDefinition().allowMultiple():
@ -1725,11 +1541,11 @@ class TableFieldWidgetWrapper(WidgetWrapper):
def parentValueChanged(self, wrapper):
value = wrapper.parameterValue()
if value in wrapper.fileBasedLayers:
self.setLayer(wrapper.fileBasedLayers[value])
if value in self.parent_file_based_layers:
self.setLayer(self.parent_file_based_layers[value])
else:
self.setLayer(value)
wrapper.fileBasedLayers[value] = self._layer
self.parent_file_based_layers[value] = self._layer
def setLayer(self, layer):
if isinstance(layer, QgsProcessingFeatureSourceDefinition):

View File

@ -32,6 +32,7 @@ from qgis.core import (QgsApplication,
QgsProcessingParameterVectorDestination,
QgsProcessingParameterRasterDestination,
QgsProcessingParameterRange,
QgsFeature,
QgsVectorLayer,
QgsProject)
from qgis.analysis import QgsNativeAlgorithms
@ -134,6 +135,10 @@ class WrappersTest(unittest.TestCase):
# dummy layer
layer = QgsVectorLayer('Point', 'test', 'memory')
# need at least one feature in order to have a selection
layer.dataProvider().addFeature(QgsFeature())
layer.selectAll()
self.assertTrue(layer.isValid())
QgsProject.instance().addMapLayer(layer)
@ -148,20 +153,15 @@ class WrappersTest(unittest.TestCase):
wrapper.setValue(layer.id())
self.assertEqual(wrapper.value(), layer.id())
# check not set
wrapper.setValue('')
self.assertFalse(wrapper.value())
# check selected only - expect a QgsProcessingFeatureSourceDefinition
wrapper.setValue(layer.id())
wrapper.use_selection_checkbox.setChecked(True)
wrapper.setValue(QgsProcessingFeatureSourceDefinition(layer.id(), True))
value = wrapper.value()
self.assertIsInstance(value, QgsProcessingFeatureSourceDefinition)
self.assertTrue(value.selectedFeaturesOnly)
self.assertEqual(value.source.staticValue(), layer.id())
# NOT selected only, expect a direct layer id or source value
wrapper.use_selection_checkbox.setChecked(False)
wrapper.setValue(QgsProcessingFeatureSourceDefinition(layer.id(), False))
value = wrapper.value()
self.assertEqual(value, layer.id())

View File

@ -201,6 +201,7 @@ SET(QGIS_GUI_SRCS
processing/qgsprocessingalgorithmdialogbase.cpp
processing/qgsprocessingconfigurationwidgets.cpp
processing/qgsprocessingguiregistry.cpp
processing/qgsprocessingmaplayercombobox.cpp
processing/qgsprocessingmatrixparameterdialog.cpp
processing/qgsprocessingmodelerparameterwidget.cpp
processing/qgsprocessingmultipleselectiondialog.cpp
@ -752,6 +753,7 @@ SET(QGIS_GUI_MOC_HDRS
processing/qgsprocessingalgorithmconfigurationwidget.h
processing/qgsprocessingalgorithmdialogbase.h
processing/qgsprocessingconfigurationwidgets.h
processing/qgsprocessingmaplayercombobox.h
processing/qgsprocessingmatrixparameterdialog.h
processing/qgsprocessingmodelerparameterwidget.h
processing/qgsprocessingmultipleselectiondialog.h

View File

@ -0,0 +1,399 @@
/***************************************************************************
qgsprocessingmaplayercombobox.cpp
-------------------------------
begin : June 2019
copyright : (C) 2019 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsprocessingmaplayercombobox.h"
#include "qgsmaplayercombobox.h"
#include "qgsmimedatautils.h"
#include "qgsprocessingparameters.h"
#include "qgssettings.h"
#include "qgsvectorlayer.h"
#include "qgsfeatureid.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QToolButton>
#include <QCheckBox>
#include <QDragEnterEvent>
///@cond PRIVATE
QgsProcessingMapLayerComboBox::QgsProcessingMapLayerComboBox( QgsProcessingParameterDefinition *parameter, QWidget *parent )
: QWidget( parent )
, mParameter( parameter )
{
QHBoxLayout *layout = new QHBoxLayout();
layout->setMargin( 0 );
layout->setContentsMargins( 0, 0, 0, 0 );
layout->setSpacing( 6 );
mCombo = new QgsMapLayerComboBox();
layout->addWidget( mCombo );
layout->setAlignment( mCombo, Qt::AlignTop );
mSelectButton = new QToolButton();
mSelectButton->setText( QStringLiteral( "" ) );
mSelectButton->setToolTip( tr( "Select file" ) );
connect( mSelectButton, &QToolButton::clicked, this, &QgsProcessingMapLayerComboBox::triggerFileSelection );
layout->addWidget( mSelectButton );
layout->setAlignment( mSelectButton, Qt::AlignTop );
QVBoxLayout *vl = new QVBoxLayout();
vl->setMargin( 0 );
vl->setContentsMargins( 0, 0, 0, 0 );
vl->setSpacing( 6 );
vl->addLayout( layout );
QgsMapLayerProxyModel::Filters filters = nullptr;
if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() )
{
mUseSelectionCheckBox = new QCheckBox( tr( "Selected features only" ) );
mUseSelectionCheckBox->setChecked( false );
mUseSelectionCheckBox->setEnabled( false );
vl->addWidget( mUseSelectionCheckBox );
}
if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() || mParameter->type() == QgsProcessingParameterVectorLayer::typeName() )
{
QList<int> dataTypes;
if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() )
dataTypes = static_cast< QgsProcessingParameterFeatureSource *>( mParameter )->dataTypes();
else if ( mParameter->type() == QgsProcessingParameterVectorLayer::typeName() )
dataTypes = static_cast< QgsProcessingParameterVectorLayer *>( mParameter )->dataTypes();
if ( dataTypes.contains( QgsProcessing::TypeVectorAnyGeometry ) || dataTypes.isEmpty() )
filters = QgsMapLayerProxyModel::HasGeometry;
if ( dataTypes.contains( QgsProcessing::TypeVectorPoint ) )
filters |= QgsMapLayerProxyModel::PointLayer;
if ( dataTypes.contains( QgsProcessing::TypeVectorLine ) )
filters |= QgsMapLayerProxyModel::LineLayer;
if ( dataTypes.contains( QgsProcessing::TypeVectorPolygon ) )
filters |= QgsMapLayerProxyModel::PolygonLayer;
if ( !filters )
filters = QgsMapLayerProxyModel::VectorLayer;
}
else if ( mParameter->type() == QgsProcessingParameterRasterLayer::typeName() )
{
filters = QgsMapLayerProxyModel::RasterLayer;
}
else if ( mParameter->type() == QgsProcessingParameterMeshLayer::typeName() )
{
filters = QgsMapLayerProxyModel::MeshLayer;
}
QgsSettings settings;
if ( settings.value( QStringLiteral( "Processing/Configuration/SHOW_CRS_DEF" ), true ).toBool() )
mCombo->setShowCrs( true );
if ( filters )
mCombo->setFilters( filters );
mCombo->setExcludedProviders( QStringList() << QStringLiteral( "grass" ) ); // not sure if this is still required...
if ( mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
{
mCombo->setAllowEmptyLayer( true );
mCombo->setLayer( nullptr );
}
connect( mCombo, &QgsMapLayerComboBox::layerChanged, this, &QgsProcessingMapLayerComboBox::onLayerChanged );
if ( mUseSelectionCheckBox )
connect( mUseSelectionCheckBox, &QCheckBox::toggled, this, [ = ]
{
if ( !mBlockChangedSignal )
emit valueChanged();
} );
setLayout( vl );
setAcceptDrops( true );
}
void QgsProcessingMapLayerComboBox::setLayer( QgsMapLayer *layer )
{
if ( layer || mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
mCombo->setLayer( layer );
}
QgsMapLayer *QgsProcessingMapLayerComboBox::currentLayer()
{
return mCombo->currentLayer();
}
QString QgsProcessingMapLayerComboBox::currentText()
{
return mCombo->currentText();
}
void QgsProcessingMapLayerComboBox::setValue( const QVariant &value, QgsProcessingContext &context )
{
QVariant val = value;
bool found = false;
bool selectedOnly = false;
if ( val.canConvert<QgsProcessingFeatureSourceDefinition>() )
{
QgsProcessingFeatureSourceDefinition fromVar = qvariant_cast<QgsProcessingFeatureSourceDefinition>( val );
val = fromVar.source;
selectedOnly = fromVar.selectedFeaturesOnly;
}
if ( val.canConvert<QgsProperty>() )
{
if ( val.value< QgsProperty >().propertyType() == QgsProperty::StaticProperty )
{
val = val.value< QgsProperty >().staticValue();
}
else
{
val = val.value< QgsProperty >().valueAsString( context.expressionContext(), mParameter->defaultValue().toString() );
}
}
QgsMapLayer *layer = qobject_cast< QgsMapLayer * >( val.value< QObject * >() );
if ( !layer && val.type() == QVariant::String )
{
layer = QgsProcessingUtils::mapLayerFromString( val.toString(), context, false );
}
if ( layer )
{
mBlockChangedSignal++;
QgsMapLayer *prevLayer = currentLayer();
setLayer( layer );
found = static_cast< bool >( currentLayer() );
bool changed = found && ( currentLayer() != prevLayer );
if ( found && mUseSelectionCheckBox )
{
const bool hasSelection = qobject_cast< QgsVectorLayer * >( layer ) && qobject_cast< QgsVectorLayer * >( layer )->selectedFeatureCount() > 0;
changed = changed | ( ( hasSelection && selectedOnly ) != mUseSelectionCheckBox->isChecked() );
if ( hasSelection )
{
mUseSelectionCheckBox->setEnabled( true );
mUseSelectionCheckBox->setChecked( selectedOnly );
}
else
{
mUseSelectionCheckBox->setChecked( false );
mUseSelectionCheckBox->setEnabled( false );
}
}
mBlockChangedSignal--;
if ( changed )
emit valueChanged(); // and ensure we only ever raise one
}
if ( !found )
{
const QString string = val.toString();
if ( !string.isEmpty() )
{
mBlockChangedSignal++;
if ( mCombo->findText( string ) < 0 )
{
QStringList additional = mCombo->additionalItems();
additional.append( string );
mCombo->setAdditionalItems( additional );
}
mCombo->setCurrentIndex( mCombo->findText( string ) ); // this may or may not throw a signal, so let's block it..
mBlockChangedSignal--;
if ( !mBlockChangedSignal )
emit valueChanged(); // and ensure we only ever raise one
}
else if ( mParameter->flags() & QgsProcessingParameterDefinition::FlagOptional )
{
mCombo->setLayer( nullptr );
}
}
}
QVariant QgsProcessingMapLayerComboBox::value() const
{
if ( QgsMapLayer *layer = mCombo->currentLayer() )
{
if ( mUseSelectionCheckBox && mUseSelectionCheckBox->isChecked() )
return QgsProcessingFeatureSourceDefinition( layer->id(), true );
else
return layer->id();
}
else
{
if ( !mCombo->currentText().isEmpty() )
{
if ( mUseSelectionCheckBox && mUseSelectionCheckBox->isChecked() )
return QgsProcessingFeatureSourceDefinition( mCombo->currentText(), true );
else
return mCombo->currentText();
}
}
return QVariant();
}
QgsMapLayer *QgsProcessingMapLayerComboBox::compatibleMapLayerFromMimeData( const QMimeData *data, bool &incompatibleLayerSelected ) const
{
incompatibleLayerSelected = false;
const QgsMimeDataUtils::UriList uriList = QgsMimeDataUtils::decodeUriList( data );
for ( const QgsMimeDataUtils::Uri &u : uriList )
{
// is this uri from the current project?
if ( QgsMapLayer *layer = u.mapLayer() )
{
if ( mCombo->mProxyModel->acceptsLayer( layer ) )
return layer;
else
{
incompatibleLayerSelected = true;
return nullptr;
}
}
}
return nullptr;
}
QString QgsProcessingMapLayerComboBox::compatibleUriFromMimeData( const QMimeData *data ) const
{
const QgsMimeDataUtils::UriList uriList = QgsMimeDataUtils::decodeUriList( data );
for ( const QgsMimeDataUtils::Uri &u : uriList )
{
if ( ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName()
|| mParameter->type() == QgsProcessingParameterVectorLayer::typeName()
|| mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
&& u.layerType == QLatin1String( "vector" ) && u.providerKey == QLatin1String( "ogr" ) )
return u.uri;
else if ( ( mParameter->type() == QgsProcessingParameterRasterLayer::typeName()
|| mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
&& u.layerType == QLatin1String( "raster" ) && u.providerKey == QLatin1String( "gdal" ) )
return u.uri;
else if ( ( mParameter->type() == QgsProcessingParameterMeshLayer::typeName()
|| mParameter->type() == QgsProcessingParameterMapLayer::typeName() )
&& u.layerType == QLatin1String( "mesh" ) && u.providerKey == QLatin1String( "mdal" ) )
return u.uri;
}
if ( !uriList.isEmpty() )
return QString();
// second chance -- files dragged from file explorer, outside of QGIS
QStringList rawPaths;
if ( data->hasUrls() )
{
const QList< QUrl > urls = data->urls();
rawPaths.reserve( urls.count() );
for ( const QUrl &url : urls )
{
const QString local = url.toLocalFile();
if ( !rawPaths.contains( local ) )
rawPaths.append( local );
}
}
if ( !data->text().isEmpty() && !rawPaths.contains( data->text() ) )
rawPaths.append( data->text() );
for ( const QString &path : qgis::as_const( rawPaths ) )
{
QFileInfo file( path );
if ( file.isFile() )
{
// TODO - we should check to see if it's a valid extension for the parameter, but that's non-trivial
return path;
}
}
return QString();
}
void QgsProcessingMapLayerComboBox::dragEnterEvent( QDragEnterEvent *event )
{
if ( !( event->possibleActions() & Qt::CopyAction ) )
return;
bool incompatibleLayerSelected = false;
QgsMapLayer *layer = compatibleMapLayerFromMimeData( event->mimeData(), incompatibleLayerSelected );
const QString uri = compatibleUriFromMimeData( event->mimeData() );
if ( layer || ( !incompatibleLayerSelected && !uri.isEmpty() ) )
{
// dragged an acceptable layer, phew
event->setDropAction( Qt::CopyAction );
event->accept();
mDragActive = true;
mCombo->mHighlight = true;
update();
}
}
void QgsProcessingMapLayerComboBox::dragLeaveEvent( QDragLeaveEvent *event )
{
QWidget::dragLeaveEvent( event );
if ( mDragActive )
{
event->accept();
mDragActive = false;
mCombo->mHighlight = false;
update();
}
}
void QgsProcessingMapLayerComboBox::dropEvent( QDropEvent *event )
{
if ( !( event->possibleActions() & Qt::CopyAction ) )
return;
bool incompatibleLayerSelected = false;
QgsMapLayer *layer = compatibleMapLayerFromMimeData( event->mimeData(), incompatibleLayerSelected );
const QString uri = compatibleUriFromMimeData( event->mimeData() );
if ( layer || ( !incompatibleLayerSelected && !uri.isEmpty() ) )
{
// dropped an acceptable layer, phew
setFocus( Qt::MouseFocusReason );
event->setDropAction( Qt::CopyAction );
event->accept();
QgsProcessingContext context;
setValue( layer ? QVariant::fromValue( layer ) : QVariant::fromValue( uri ), context );
}
mDragActive = false;
mCombo->mHighlight = false;
update();
}
void QgsProcessingMapLayerComboBox::onLayerChanged( QgsMapLayer *layer )
{
if ( mParameter->type() == QgsProcessingParameterFeatureSource::typeName() )
{
if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
{
if ( QgsVectorLayer *prevLayer = qobject_cast< QgsVectorLayer * >( mPrevLayer ) )
{
disconnect( prevLayer, &QgsVectorLayer::selectionChanged, this, &QgsProcessingMapLayerComboBox::selectionChanged );
}
if ( vl->selectedFeatureCount() == 0 )
mUseSelectionCheckBox->setChecked( false );
mUseSelectionCheckBox->setEnabled( vl->selectedFeatureCount() > 0 );
connect( vl, &QgsVectorLayer::selectionChanged, this, &QgsProcessingMapLayerComboBox::selectionChanged );
}
}
mPrevLayer = layer;
if ( !mBlockChangedSignal )
emit valueChanged();
}
void QgsProcessingMapLayerComboBox::selectionChanged( const QgsFeatureIds &selected, const QgsFeatureIds &, bool )
{
if ( selected.isEmpty() )
mUseSelectionCheckBox->setChecked( false );
mUseSelectionCheckBox->setEnabled( !selected.isEmpty() );
}
///@endcond

View File

@ -0,0 +1,130 @@
/***************************************************************************
qgsprocessingmaplayercombobox.h
-----------------------------
begin : June 2019
copyright : (C) 2019 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSPROCESSINGMAPLAYERCOMBOBOX_H
#define QGSPROCESSINGMAPLAYERCOMBOBOX_H
#include "qgis.h"
#include "qgis_gui.h"
#include <QTreeView>
#include "qgsprocessingtoolboxmodel.h"
#include "qgsfeatureid.h"
#include "qgsmimedatautils.h"
#include "qgsprocessingcontext.h"
class QgsMapLayerComboBox;
class QToolButton;
class QCheckBox;
class QgsProcessingParameterDefinition;
///@cond PRIVATE
/**
* Processing map layer combo box.
* \ingroup gui
* \warning Not part of stable API and may change in future QGIS releases.
* \since QGIS 3.8
*/
class GUI_EXPORT QgsProcessingMapLayerComboBox : public QWidget
{
Q_OBJECT
public:
/**
* Constructor for QgsProcessingMapLayerComboBox, with the specified \a parameter definition.
*/
QgsProcessingMapLayerComboBox( QgsProcessingParameterDefinition *parameter, QWidget *parent = nullptr );
/**
* Sets the combo box to the specified \a layer, if \a layer is compatible with the
* widget's parameter definition.
*/
void setLayer( QgsMapLayer *layer );
/**
* Returns the current layer selected in the combobox, or NULLPTR if the selection cannot
* be represented as a map layer.
*
* \warning Prefer calling value() instead, as it correctly encapsulates all valid
* values which can be represented by the widget.
*
* \see currentText()
*/
QgsMapLayer *currentLayer();
/**
* Returns the current text of the selected item in the combobox.
*
* \warning Prefer calling value() instead, as it correctly encapsulates all valid
* values which can be represented by the widget.
*
* \see currentLayer()
*/
QString currentText();
/**
* Sets the \a value shown in the widget.
*
* \see value()
*/
void setValue( const QVariant &value, QgsProcessingContext &context );
/**
* Returns the current value of the widget.
*
* \see setValue()
*/
QVariant value() const;
signals:
/**
* Emitted whenever the value is changed in the widget.
*/
void valueChanged();
/**
* Emitted when the widget has triggered a file selection operation (to be
* handled in Python for now).
*/
void triggerFileSelection();
protected:
void dragEnterEvent( QDragEnterEvent *event ) override;
void dragLeaveEvent( QDragLeaveEvent *event ) override;
void dropEvent( QDropEvent *event ) override;
private slots:
void onLayerChanged( QgsMapLayer *layer );
void selectionChanged( const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect );
private:
QgsProcessingParameterDefinition *mParameter = nullptr;
QgsMapLayerComboBox *mCombo = nullptr;
QToolButton *mSelectButton = nullptr;
QCheckBox *mUseSelectionCheckBox = nullptr;
bool mDragActive = false;
QPointer< QgsMapLayer> mPrevLayer;
int mBlockChangedSignal = 0;
QgsMapLayer *compatibleMapLayerFromMimeData( const QMimeData *data, bool &incompatibleLayerSelected ) const;
QString compatibleUriFromMimeData( const QMimeData *data ) const;
};
///@endcond
#endif // QGSPROCESSINGMAPLAYERCOMBOBOX_H

View File

@ -208,7 +208,7 @@ void QgsMapLayerComboBox::dropEvent( QDropEvent *event )
void QgsMapLayerComboBox::paintEvent( QPaintEvent *e )
{
QComboBox::paintEvent( e );
if ( mDragActive )
if ( mDragActive || mHighlight )
{
QPainter p( this );
int width = 2; // width of highlight rectangle inside frame

View File

@ -153,12 +153,15 @@ class GUI_EXPORT QgsMapLayerComboBox : public QComboBox
private:
QgsMapLayerProxyModel *mProxyModel = nullptr;
bool mDragActive = false;
bool mHighlight = false;
/**
* Returns a map layer, compatible with the filters set for the combo box, from
* the specified mime \a data (if possible!).
*/
QgsMapLayer *compatibleMapLayerFromMimeData( const QMimeData *data ) const;
friend class QgsProcessingMapLayerComboBox;
};
#endif // QGSMAPLAYERCOMBOBOX_H

View File

@ -36,6 +36,7 @@
#include "qgsprocessingwidgetwrapperimpl.h"
#include "qgsprocessingmodelerparameterwidget.h"
#include "qgsprocessingparameters.h"
#include "qgsprocessingmaplayercombobox.h"
#include "qgsnativealgorithms.h"
#include "processing/models/qgsprocessingmodelalgorithm.h"
#include "qgsxmlutils.h"
@ -58,6 +59,8 @@
#include "qgslayoutitemcombobox.h"
#include "qgslayoutitemlabel.h"
#include "qgsscalewidget.h"
#include "mesh/qgsmeshlayer.h"
#include "mesh/qgsmeshdataprovider.h"
class TestParamType : public QgsProcessingParameterDefinition
{
@ -181,6 +184,7 @@ class TestProcessingGui : public QObject
void testLayoutItemWrapper();
void testPointPanel();
void testPointWrapper();
void mapLayerComboBox();
private:
@ -3011,6 +3015,416 @@ void TestProcessingGui::testPointWrapper()
testWrapper( QgsProcessingGui::Modeler );
}
void TestProcessingGui::mapLayerComboBox()
{
QgsProject::instance()->removeAllMapLayers();
QgsProcessingContext context;
context.setProject( QgsProject::instance() );
// feature source param
std::unique_ptr< QgsProcessingParameterDefinition > param( new QgsProcessingParameterFeatureSource( QStringLiteral( "param" ), QString() ) );
std::unique_ptr< QgsProcessingMapLayerComboBox> combo = qgis::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
QSignalSpy spy( combo.get(), &QgsProcessingMapLayerComboBox::valueChanged );
QVERIFY( !combo->value().isValid() );
combo->setValue( QStringLiteral( "file path" ), context );
QCOMPARE( spy.count(), 1 );
QCOMPARE( combo->value().toString(), QStringLiteral( "file path" ) );
QVERIFY( !combo->currentLayer() );
QCOMPARE( spy.count(), 1 );
combo->setValue( QVariant(), context ); // not possible, it's not an optional param
QCOMPARE( combo->value().toString(), QStringLiteral( "file path" ) );
QVERIFY( !combo->currentLayer() );
QCOMPARE( spy.count(), 1 );
combo->setValue( QStringLiteral( "file path 2" ), context );
QCOMPARE( combo->value().toString(), QStringLiteral( "file path 2" ) );
QVERIFY( !combo->currentLayer() );
QCOMPARE( spy.count(), 2 );
combo->setValue( QStringLiteral( "file path" ), context );
QCOMPARE( combo->value().toString(), QStringLiteral( "file path" ) );
QVERIFY( !combo->currentLayer() );
QCOMPARE( spy.count(), 3 );
combo->setLayer( nullptr ); // not possible, not optional
QCOMPARE( combo->value().toString(), QStringLiteral( "file path" ) );
QVERIFY( !combo->currentLayer() );
QCOMPARE( spy.count(), 3 );
// project layers
QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
QgsFeature f;
vl->dataProvider()->addFeature( f );
QgsProject::instance()->addMapLayer( vl );
QVERIFY( vl->isValid() );
QgsVectorLayer *vl2 = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "l2" ), QStringLiteral( "memory" ) );
vl2->dataProvider()->addFeature( f );
QgsProject::instance()->addMapLayer( vl2 );
QVERIFY( vl2->isValid() );
QCOMPARE( combo->value().toString(), QStringLiteral( "file path" ) );
QVERIFY( !combo->currentLayer() );
QCOMPARE( spy.count(), 3 );
combo->setLayer( vl );
QCOMPARE( combo->currentLayer(), vl );
QCOMPARE( combo->value().toString(), vl->id() );
QVERIFY( combo->currentText().startsWith( vl->name() ) );
QCOMPARE( spy.count(), 4 );
combo->setLayer( vl );
QCOMPARE( spy.count(), 4 );
combo->setLayer( vl2 );
QCOMPARE( combo->value().toString(), vl2->id() );
QVERIFY( combo->currentText().startsWith( vl2->name() ) );
QCOMPARE( spy.count(), 5 );
combo->setValue( QStringLiteral( "file path" ), context );
QCOMPARE( combo->value().toString(), QStringLiteral( "file path" ) );
QVERIFY( !combo->currentLayer() );
QCOMPARE( spy.count(), 6 );
// setting feature source def, i.e. with selection
QgsProcessingFeatureSourceDefinition sourceDef( vl2->id(), false );
combo->setValue( sourceDef, context );
QCOMPARE( combo->value().toString(), vl2->id() );
QVERIFY( combo->currentText().startsWith( vl2->name() ) );
QCOMPARE( spy.count(), 7 );
// asking for selected features only, but no selection in layer, won't be allowed
sourceDef = QgsProcessingFeatureSourceDefinition( vl2->id(), true );
combo->setValue( sourceDef, context );
QCOMPARE( combo->value().toString(), vl2->id() );
QVERIFY( combo->currentText().startsWith( vl2->name() ) );
QCOMPARE( spy.count(), 7 ); // no change
// now make a selection in the layer, and repeat
vl2->selectAll();
combo->setValue( sourceDef, context );
QVERIFY( combo->value().canConvert< QgsProcessingFeatureSourceDefinition >() );
QCOMPARE( combo->value().value< QgsProcessingFeatureSourceDefinition >().source.staticValue().toString(), vl2->id() );
QVERIFY( combo->value().value< QgsProcessingFeatureSourceDefinition >().selectedFeaturesOnly );
QVERIFY( combo->currentText().startsWith( vl2->name() ) );
QCOMPARE( spy.count(), 8 );
// remove layer selection, and check result...
vl2->removeSelection();
QCOMPARE( combo->value().toString(), vl2->id() );
QVERIFY( combo->currentText().startsWith( vl2->name() ) );
QCOMPARE( spy.count(), 9 );
// phew, nearly there. Let's check another variation
vl2->selectAll();
combo->setValue( sourceDef, context );
QVERIFY( combo->value().canConvert< QgsProcessingFeatureSourceDefinition >() );
QCOMPARE( spy.count(), 10 );
combo->setValue( QVariant::fromValue( vl ), context );
QCOMPARE( combo->value().toString(), vl->id() );
QVERIFY( combo->currentText().startsWith( vl->name() ) );
QCOMPARE( spy.count(), 11 );
// one last variation - selection to selection
combo->setValue( sourceDef, context );
QCOMPARE( spy.count(), 12 );
QVERIFY( combo->value().value< QgsProcessingFeatureSourceDefinition >().selectedFeaturesOnly );
vl->selectAll();
sourceDef = QgsProcessingFeatureSourceDefinition( vl->id(), true );
combo->setValue( sourceDef, context );
// except "selected only" state to remain
QVERIFY( combo->value().canConvert< QgsProcessingFeatureSourceDefinition >() );
QCOMPARE( combo->value().value< QgsProcessingFeatureSourceDefinition >().source.staticValue().toString(), vl->id() );
QVERIFY( combo->value().value< QgsProcessingFeatureSourceDefinition >().selectedFeaturesOnly );
QVERIFY( combo->currentText().startsWith( vl->name() ) );
QCOMPARE( spy.count(), 13 );
// setup a project with a range of layer types
QgsProject::instance()->removeAllMapLayers();
QgsVectorLayer *point = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
QgsProject::instance()->addMapLayer( point );
QgsVectorLayer *line = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
QgsProject::instance()->addMapLayer( line );
QgsVectorLayer *polygon = new QgsVectorLayer( QStringLiteral( "Polygon" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
QgsProject::instance()->addMapLayer( polygon );
QgsVectorLayer *noGeom = new QgsVectorLayer( QStringLiteral( "None" ), QStringLiteral( "l1" ), QStringLiteral( "memory" ) );
QgsProject::instance()->addMapLayer( noGeom );
QgsMeshLayer *mesh = new QgsMeshLayer( QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle.2dm", QStringLiteral( "Triangle and Quad Mdal" ), QStringLiteral( "mdal" ) );
mesh->dataProvider()->addDataset( QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle_vertex_scalar_with_inactive_face.dat" );
QVERIFY( mesh->isValid() );
QgsProject::instance()->addMapLayer( mesh );
QgsRasterLayer *raster = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/raster/band1_byte_ct_epsg4326.tif", QStringLiteral( "band1_byte" ) );
QgsProject::instance()->addMapLayer( raster );
// map layer param, all types are acceptable
param = qgis::make_unique< QgsProcessingParameterMapLayer> ( QStringLiteral( "param" ), QString() );
combo = qgis::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
combo->setLayer( point );
QCOMPARE( combo->currentLayer(), point );
combo->setLayer( line );
QCOMPARE( combo->currentLayer(), line );
combo->setLayer( polygon );
QCOMPARE( combo->currentLayer(), polygon );
combo->setLayer( noGeom );
QCOMPARE( combo->currentLayer(), noGeom );
combo->setLayer( mesh );
QCOMPARE( combo->currentLayer(), mesh );
combo->setLayer( raster );
QCOMPARE( combo->currentLayer(), raster );
// raster layer param, only raster types are acceptable
param = qgis::make_unique< QgsProcessingParameterRasterLayer> ( QStringLiteral( "param" ), QString() );
combo = qgis::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
combo->setLayer( point );
QVERIFY( !combo->currentLayer() );
combo->setLayer( line );
QVERIFY( !combo->currentLayer() );
combo->setLayer( polygon );
QVERIFY( !combo->currentLayer() );
combo->setLayer( noGeom );
QVERIFY( !combo->currentLayer() );
combo->setLayer( mesh );
QVERIFY( !combo->currentLayer() );
combo->setLayer( raster );
QCOMPARE( combo->currentLayer(), raster );
// mesh layer parm, only mesh types are acceptable
param = qgis::make_unique< QgsProcessingParameterMeshLayer> ( QStringLiteral( "param" ), QString() );
combo = qgis::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
combo->setLayer( point );
QVERIFY( !combo->currentLayer() );
combo->setLayer( line );
QVERIFY( !combo->currentLayer() );
combo->setLayer( polygon );
QVERIFY( !combo->currentLayer() );
combo->setLayer( noGeom );
QVERIFY( !combo->currentLayer() );
combo->setLayer( mesh );
QCOMPARE( combo->currentLayer(), mesh );
combo->setLayer( raster );
QVERIFY( !combo->currentLayer() );
// feature source and vector layer params
// if not specified, the default is any vector layer with geometry
param = qgis::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ) );
combo = qgis::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
auto param2 = qgis::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ) );
auto combo2 = qgis::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
combo->setLayer( point );
QCOMPARE( combo->currentLayer(), point );
combo2->setLayer( point );
QCOMPARE( combo2->currentLayer(), point );
combo->setLayer( line );
QCOMPARE( combo->currentLayer(), line );
combo2->setLayer( line );
QCOMPARE( combo2->currentLayer(), line );
combo->setLayer( polygon );
QCOMPARE( combo->currentLayer(), polygon );
combo2->setLayer( polygon );
QCOMPARE( combo2->currentLayer(), polygon );
combo->setLayer( noGeom );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( noGeom );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( mesh );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( mesh );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( raster );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( raster );
QVERIFY( !combo2->currentLayer() );
// point layer
param = qgis::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorPoint );
combo = qgis::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
param2 = qgis::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorPoint );
combo2 = qgis::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
combo->setLayer( point );
QCOMPARE( combo->currentLayer(), point );
combo2->setLayer( point );
QCOMPARE( combo2->currentLayer(), point );
combo->setLayer( line );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( line );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( polygon );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( polygon );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( noGeom );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( noGeom );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( mesh );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( mesh );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( raster );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( raster );
QVERIFY( !combo2->currentLayer() );
// line layer
param = qgis::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorLine );
combo = qgis::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
param2 = qgis::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorLine );
combo2 = qgis::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
combo->setLayer( point );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( point );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( line );
QCOMPARE( combo->currentLayer(), line );
combo2->setLayer( line );
QCOMPARE( combo2->currentLayer(), line );
combo->setLayer( polygon );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( polygon );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( noGeom );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( noGeom );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( mesh );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( mesh );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( raster );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( raster );
QVERIFY( !combo2->currentLayer() );
// polygon
param = qgis::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorPolygon );
combo = qgis::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
param2 = qgis::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorPolygon );
combo2 = qgis::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
combo->setLayer( point );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( point );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( line );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( line );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( polygon );
QCOMPARE( combo->currentLayer(), polygon );
combo2->setLayer( polygon );
QCOMPARE( combo2->currentLayer(), polygon );
combo->setLayer( noGeom );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( noGeom );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( mesh );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( mesh );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( raster );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( raster );
QVERIFY( !combo2->currentLayer() );
// no geom
param = qgis::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVector );
combo = qgis::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
param2 = qgis::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVector );
combo2 = qgis::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
combo->setLayer( point );
QCOMPARE( combo->currentLayer(), point );
combo2->setLayer( point );
QCOMPARE( combo2->currentLayer(), point );
combo->setLayer( line );
QCOMPARE( combo->currentLayer(), line );
combo2->setLayer( line );
QCOMPARE( combo2->currentLayer(), line );
combo->setLayer( polygon );
QCOMPARE( combo->currentLayer(), polygon );
combo2->setLayer( polygon );
QCOMPARE( combo2->currentLayer(), polygon );
combo->setLayer( noGeom );
QCOMPARE( combo->currentLayer(), noGeom );
combo2->setLayer( noGeom );
QCOMPARE( combo2->currentLayer(), noGeom );
combo->setLayer( mesh );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( mesh );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( raster );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( raster );
QVERIFY( !combo2->currentLayer() );
// any geom
param = qgis::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorAnyGeometry );
combo = qgis::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
param2 = qgis::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorAnyGeometry );
combo2 = qgis::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
combo->setLayer( point );
QCOMPARE( combo->currentLayer(), point );
combo2->setLayer( point );
QCOMPARE( combo2->currentLayer(), point );
combo->setLayer( line );
QCOMPARE( combo->currentLayer(), line );
combo2->setLayer( line );
QCOMPARE( combo2->currentLayer(), line );
combo->setLayer( polygon );
QCOMPARE( combo->currentLayer(), polygon );
combo2->setLayer( polygon );
QCOMPARE( combo2->currentLayer(), polygon );
combo->setLayer( noGeom );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( noGeom );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( mesh );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( mesh );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( raster );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( raster );
QVERIFY( !combo2->currentLayer() );
// combination point and line only
param = qgis::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorLine );
combo = qgis::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
param2 = qgis::make_unique< QgsProcessingParameterFeatureSource> ( QStringLiteral( "param" ), QString(), QList< int>() << QgsProcessing::TypeVectorPoint << QgsProcessing::TypeVectorLine );
combo2 = qgis::make_unique< QgsProcessingMapLayerComboBox >( param2.get() );
combo->setLayer( point );
QCOMPARE( combo->currentLayer(), point );
combo2->setLayer( point );
QCOMPARE( combo2->currentLayer(), point );
combo->setLayer( line );
QCOMPARE( combo->currentLayer(), line );
combo2->setLayer( line );
QCOMPARE( combo2->currentLayer(), line );
combo->setLayer( polygon );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( polygon );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( noGeom );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( noGeom );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( mesh );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( mesh );
QVERIFY( !combo2->currentLayer() );
combo->setLayer( raster );
QVERIFY( !combo->currentLayer() );
combo2->setLayer( raster );
QVERIFY( !combo2->currentLayer() );
// optional
param = qgis::make_unique< QgsProcessingParameterVectorLayer> ( QStringLiteral( "param" ), QString(), QList< int>(), QVariant(), true );
combo = qgis::make_unique< QgsProcessingMapLayerComboBox >( param.get() );
combo->setLayer( point );
QCOMPARE( combo->currentLayer(), point );
combo->setLayer( nullptr );
QVERIFY( !combo->currentLayer() );
QVERIFY( !combo->value().isValid() );
combo->setLayer( point );
QCOMPARE( combo->currentLayer(), point );
combo->setValue( QVariant(), context );
QVERIFY( !combo->currentLayer() );
QVERIFY( !combo->value().isValid() );
}
void TestProcessingGui::cleanupTempDir()
{
QDir tmpDir = QDir( mTempDir );