Skip to content

Commit

Permalink
[processing] port field parameter definition widget to C++, expose
Browse files Browse the repository at this point in the history
defaultToAll option to modeler parameter definition dialog
  • Loading branch information
alexbruy authored and nyalldawson committed Apr 4, 2020
1 parent a2ed502 commit ebd0a72
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 67 deletions.
Expand Up @@ -74,17 +74,15 @@ class ModelerParameterDefinitionDialog(QDialog):

@staticmethod
def use_legacy_dialog(param=None, paramType=None):
if paramType in (parameters.PARAMETER_TABLE_FIELD,
parameters.PARAMETER_VECTOR,
if paramType in (parameters.PARAMETER_VECTOR,
parameters.PARAMETER_TABLE,
parameters.PARAMETER_MULTIPLE,
parameters.PARAMETER_NUMBER,
parameters.PARAMETER_DISTANCE,
parameters.PARAMETER_SCALE,
parameters.PARAMETER_MAP_LAYER):
return True
elif isinstance(param, (QgsProcessingParameterField,
QgsProcessingParameterFeatureSource,
elif isinstance(param, (QgsProcessingParameterFeatureSource,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMultipleLayers,
QgsProcessingParameterNumber,
Expand Down Expand Up @@ -137,53 +135,7 @@ def setupUi(self):
if isinstance(self.param, QgsProcessingParameterDefinition):
self.nameTextBox.setText(self.param.description())

if self.paramType == parameters.PARAMETER_TABLE_FIELD or \
isinstance(self.param, QgsProcessingParameterField):
self.verticalLayout.addWidget(QLabel(self.tr('Parent layer')))
self.parentCombo = QComboBox()
idx = 0
for param in list(self.alg.parameterComponents().values()):
definition = self.alg.parameterDefinition(param.parameterName())
if isinstance(definition, (QgsProcessingParameterFeatureSource, QgsProcessingParameterVectorLayer)):
self.parentCombo.addItem(definition.description(), definition.name())
if self.param is not None:
if self.param.parentLayerParameterName() == definition.name():
self.parentCombo.setCurrentIndex(idx)
idx += 1
self.verticalLayout.addWidget(self.parentCombo)

# add the datatype selector
self.verticalLayout.addWidget(QLabel(self.tr('Allowed data type')))
self.datatypeCombo = QComboBox()
self.datatypeCombo.addItem(self.tr('Any'), -1)
self.datatypeCombo.addItem(self.tr('Number'), 0)
self.datatypeCombo.addItem(self.tr('String'), 1)
self.datatypeCombo.addItem(self.tr('Date/time'), 2)
self.verticalLayout.addWidget(self.datatypeCombo)

if self.param is not None and self.param.dataType() is not None:
# QComboBoxes indexes start at 0,
# self.param.datatype start with -1 that is why I need to do +1
datatypeIndex = self.param.dataType() + 1
self.datatypeCombo.setCurrentIndex(datatypeIndex)

self.multipleCheck = QCheckBox()
self.multipleCheck.setText(self.tr('Accept multiple fields'))
self.multipleCheck.setChecked(False)
if self.param is not None:
self.multipleCheck.setChecked(self.param.allowMultiple())
self.verticalLayout.addWidget(self.multipleCheck)

self.verticalLayout.addWidget(QLabel(self.tr('Default value')))
self.defaultTextBox = QLineEdit()
self.defaultTextBox.setToolTip(
self.tr('Default field name, or ; separated list of field names for multiple field parameters'))
if self.param is not None:
default = self.param.defaultValue()
if default is not None:
self.defaultTextBox.setText(str(default))
self.verticalLayout.addWidget(self.defaultTextBox)
elif (self.paramType in (
if (self.paramType in (
parameters.PARAMETER_VECTOR, parameters.PARAMETER_TABLE) or
isinstance(self.param, (QgsProcessingParameterFeatureSource, QgsProcessingParameterVectorLayer))):
self.verticalLayout.addWidget(QLabel(self.tr('Geometry type')))
Expand Down Expand Up @@ -380,22 +332,9 @@ def accept(self):
i += 1
else:
name = self.param.name()
if (self.paramType == parameters.PARAMETER_TABLE_FIELD or
isinstance(self.param, QgsProcessingParameterField)):
if self.parentCombo.currentIndex() < 0:
QMessageBox.warning(self, self.tr('Unable to define parameter'),
self.tr('Wrong or missing parameter values'))
return
parent = self.parentCombo.currentData()
datatype = self.datatypeCombo.currentData()
default = self.defaultTextBox.text()
if not default:
default = None
self.param = QgsProcessingParameterField(name, description, defaultValue=default,
parentLayerParameterName=parent, type=datatype,
allowMultiple=self.multipleCheck.isChecked())
elif (self.paramType == parameters.PARAMETER_MAP_LAYER or
isinstance(self.param, QgsProcessingParameterMapLayer)):

if (self.paramType == parameters.PARAMETER_MAP_LAYER
or isinstance(self.param, QgsProcessingParameterMapLayer)):
self.param = QgsProcessingParameterMapLayer(
name, description, types=[self.datatypeCombo.currentData()])
elif (self.paramType == parameters.PARAMETER_RASTER or
Expand Down
104 changes: 104 additions & 0 deletions src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp
Expand Up @@ -3402,6 +3402,105 @@ void QgsProcessingFieldPanelWidget::updateSummaryText()
// QgsProcessingFieldWidgetWrapper
//

QgsProcessingFieldParameterDefinitionWidget::QgsProcessingFieldParameterDefinitionWidget( QgsProcessingContext &context, const QgsProcessingParameterWidgetContext &widgetContext, const QgsProcessingParameterDefinition *definition, const QgsProcessingAlgorithm *algorithm, QWidget *parent )
: QgsProcessingAbstractParameterDefinitionWidget( context, widgetContext, definition, algorithm, parent )
{
QVBoxLayout *vlayout = new QVBoxLayout();
vlayout->setMargin( 0 );
vlayout->setContentsMargins( 0, 0, 0, 0 );

vlayout->addWidget( new QLabel( tr( "Parent layer" ) ) );
mParentLayerComboBox = new QComboBox();

QString initialParent;
if ( const QgsProcessingParameterField *fieldParam = dynamic_cast<const QgsProcessingParameterField *>( definition ) )
initialParent = fieldParam->parentLayerParameterName();

if ( widgetContext.model() )
{
// populate combo box with other model input choices
const QMap<QString, QgsProcessingModelParameter> components = widgetContext.model()->parameterComponents();
for ( auto it = components.constBegin(); it != components.constEnd(); ++it )
{
if ( const QgsProcessingParameterFeatureSource *definition = dynamic_cast< const QgsProcessingParameterFeatureSource * >( widgetContext.model()->parameterDefinition( it.value().parameterName() ) ) )
{
mParentLayerComboBox-> addItem( definition->description(), definition->name() );
if ( !initialParent.isEmpty() && initialParent == definition->name() )
{
mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 );
}
}
else if ( const QgsProcessingParameterVectorLayer *definition = dynamic_cast< const QgsProcessingParameterVectorLayer * >( widgetContext.model()->parameterDefinition( it.value().parameterName() ) ) )
{
mParentLayerComboBox-> addItem( definition->description(), definition->name() );
if ( !initialParent.isEmpty() && initialParent == definition->name() )
{
mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 );
}
}
}
}

if ( mParentLayerComboBox->count() == 0 && !initialParent.isEmpty() )
{
// if no parent candidates found, we just add the existing one as a placeholder
mParentLayerComboBox->addItem( initialParent, initialParent );
mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 );
}

vlayout->addWidget( mParentLayerComboBox );

vlayout->addWidget( new QLabel( tr( "Allowed data type" ) ) );
mDataTypeComboBox = new QComboBox();
mDataTypeComboBox->addItem( tr( "Any" ), QgsProcessingParameterField::Any );
mDataTypeComboBox->addItem( tr( "Number" ), QgsProcessingParameterField::Numeric );
mDataTypeComboBox->addItem( tr( "String" ), QgsProcessingParameterField::String );
mDataTypeComboBox->addItem( tr( "Date/time" ), QgsProcessingParameterField::DateTime );
if ( const QgsProcessingParameterField *fieldParam = dynamic_cast<const QgsProcessingParameterField *>( definition ) )
mDataTypeComboBox->setCurrentIndex( fieldParam->dataType() + 1 ); // QComboBoxes indexes start at 0, datatype start with -1 that is why I need to do +1

vlayout->addWidget( mDataTypeComboBox );

mAllowMultipleCheckBox = new QCheckBox( tr( "Accept multiple fields" ) );
if ( const QgsProcessingParameterField *fieldParam = dynamic_cast<const QgsProcessingParameterField *>( definition ) )
mAllowMultipleCheckBox->setChecked( fieldParam->allowMultiple() );

vlayout->addWidget( mAllowMultipleCheckBox );

mDefaultToAllCheckBox = new QCheckBox( tr( "Select all fields by default" ) );
mDefaultToAllCheckBox->setEnabled( mAllowMultipleCheckBox->isChecked() );
if ( const QgsProcessingParameterField *fieldParam = dynamic_cast<const QgsProcessingParameterField *>( definition ) )
mDefaultToAllCheckBox->setChecked( fieldParam->defaultToAllFields() );

vlayout->addWidget( mDefaultToAllCheckBox );

connect( mAllowMultipleCheckBox, &QCheckBox::stateChanged, this, [ = ]
{
mDefaultToAllCheckBox->setEnabled( mAllowMultipleCheckBox->isChecked() );
} );

vlayout->addWidget( new QLabel( tr( "Default value" ) ) );

mDefaultLineEdit = new QLineEdit();
mDefaultLineEdit->setToolTip( tr( "Default field name, or ; separated list of field names for multiple field parameters" ) );
if ( const QgsProcessingParameterField *fieldParam = dynamic_cast<const QgsProcessingParameterField *>( definition ) )
{
const QStringList fields = QgsProcessingParameters::parameterAsFields( fieldParam, fieldParam->defaultValue(), context );
mDefaultLineEdit->setText( fields.join( ';' ) );
}
vlayout->addWidget( mDefaultLineEdit );

setLayout( vlayout );
}

QgsProcessingParameterDefinition *QgsProcessingFieldParameterDefinitionWidget::createParameter( const QString &name, const QString &description, QgsProcessingParameterDefinition::Flags flags ) const
{
QgsProcessingParameterField::DataType dataType = static_cast< QgsProcessingParameterField::DataType >( mDataTypeComboBox->currentData().toInt() );
auto param = qgis::make_unique< QgsProcessingParameterField >( name, description, mDefaultLineEdit->text(), mParentLayerComboBox->currentData().toString(), dataType, mAllowMultipleCheckBox->isChecked(), false, mDefaultToAllCheckBox->isChecked() );
param->setFlags( flags );
return param.release();
}

QgsProcessingFieldWidgetWrapper::QgsProcessingFieldWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type, QWidget *parent )
: QgsAbstractProcessingParameterWidgetWrapper( parameter, type, parent )
{
Expand Down Expand Up @@ -3705,6 +3804,11 @@ QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingFieldWidgetWrapper::cr
return new QgsProcessingFieldWidgetWrapper( parameter, type );
}

QgsProcessingAbstractParameterDefinitionWidget *QgsProcessingFieldWidgetWrapper::createParameterDefinitionWidget( QgsProcessingContext &context, const QgsProcessingParameterWidgetContext &widgetContext, const QgsProcessingParameterDefinition *definition, const QgsProcessingAlgorithm *algorithm )
{
return new QgsProcessingFieldParameterDefinitionWidget( context, widgetContext, definition, algorithm );
}

//
// QgsProcessingMapThemeWidgetWrapper
//
Expand Down
24 changes: 24 additions & 0 deletions src/gui/processing/qgsprocessingwidgetwrapperimpl.h
Expand Up @@ -1142,6 +1142,25 @@ class GUI_EXPORT QgsProcessingFieldPanelWidget : public QWidget
friend class TestProcessingGui;
};

class GUI_EXPORT QgsProcessingFieldParameterDefinitionWidget : public QgsProcessingAbstractParameterDefinitionWidget
{
Q_OBJECT
public:

QgsProcessingFieldParameterDefinitionWidget( QgsProcessingContext &context,
const QgsProcessingParameterWidgetContext &widgetContext,
const QgsProcessingParameterDefinition *definition = nullptr,
const QgsProcessingAlgorithm *algorithm = nullptr, QWidget *parent SIP_TRANSFERTHIS = nullptr );
QgsProcessingParameterDefinition *createParameter( const QString &name, const QString &description, QgsProcessingParameterDefinition::Flags flags ) const override;

private:

QComboBox *mParentLayerComboBox = nullptr;
QComboBox *mDataTypeComboBox = nullptr;
QLineEdit *mDefaultLineEdit = nullptr;
QCheckBox *mAllowMultipleCheckBox = nullptr;
QCheckBox *mDefaultToAllCheckBox = nullptr;
};

class GUI_EXPORT QgsProcessingFieldWidgetWrapper : public QgsAbstractProcessingParameterWidgetWrapper, public QgsProcessingParameterWidgetFactoryInterface
{
Expand All @@ -1155,6 +1174,11 @@ class GUI_EXPORT QgsProcessingFieldWidgetWrapper : public QgsAbstractProcessingP
// QgsProcessingParameterWidgetFactoryInterface
QString parameterType() const override;
QgsAbstractProcessingParameterWidgetWrapper *createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type ) override;
QgsProcessingAbstractParameterDefinitionWidget *createParameterDefinitionWidget(
QgsProcessingContext &context,
const QgsProcessingParameterWidgetContext &widgetContext,
const QgsProcessingParameterDefinition *definition = nullptr,
const QgsProcessingAlgorithm *algorithm = nullptr ) override;

// QgsProcessingParameterWidgetWrapper interface
QWidget *createWidget() override SIP_FACTORY;
Expand Down
40 changes: 40 additions & 0 deletions tests/src/gui/testprocessinggui.cpp
Expand Up @@ -2796,6 +2796,46 @@ void TestProcessingGui::testFieldWrapper()

// modeler wrapper
testWrapper( QgsProcessingGui::Modeler );

// config widget
QgsProcessingParameterWidgetContext widgetContext;
QgsProcessingContext context;
std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = qgis::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "field" ), context, widgetContext );
std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) );
QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory
QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );

// using a parameter definition as initial values
QgsProcessingParameterField fieldParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "field_name" ), QStringLiteral( "parent" ) );
widget = qgis::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "field" ), context, widgetContext, &fieldParam );
def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) );
QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) );
QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->defaultValue().toString(), QStringLiteral( "field_name" ) );
QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->parentLayerParameterName(), QStringLiteral( "parent" ) );
QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->dataType(), QgsProcessingParameterField::Any );
QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->allowMultiple(), false );
QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->defaultToAllFields(), false );
fieldParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional );
fieldParam.setParentLayerParameterName( QString() );
fieldParam.setAllowMultiple( true );
fieldParam.setDefaultToAllFields( true );
fieldParam.setDataType( QgsProcessingParameterField::String );
fieldParam.setDefaultValue( QStringLiteral( "field_1;field_2" ) );
widget = qgis::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "field" ), context, widgetContext, &fieldParam );
def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) );
QCOMPARE( def->name(), QStringLiteral( "param_name" ) );
QCOMPARE( def->description(), QStringLiteral( "test desc" ) );
QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional );
QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced );
QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->defaultValue().toString(), QStringLiteral( "field_1;field_2" ) );
QVERIFY( static_cast< QgsProcessingParameterBand * >( def.get() )->parentLayerParameterName().isEmpty() );
QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->dataType(), QgsProcessingParameterField::String );
QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->allowMultiple(), true );
QCOMPARE( static_cast< QgsProcessingParameterField * >( def.get() )->defaultToAllFields(), true );
}

void TestProcessingGui::testMultipleSelectionDialog()
Expand Down

0 comments on commit ebd0a72

Please sign in to comment.