[processing] Port field mapper wrapper and widget to c++

Fixes #36706
This commit is contained in:
Nyall Dawson 2020-06-01 17:12:47 +10:00
parent dbe9aa0902
commit 146094f6b2
11 changed files with 641 additions and 205 deletions

View File

@ -92,6 +92,13 @@ corresponding expression.
void scrollTo( const QModelIndex &index ) const;
%Docstring
Scroll the fields view to ``index``
%End
signals:
void changed();
%Docstring
Emitted when the fields defined in the widget are changed.
%End
public slots:

View File

@ -54,170 +54,13 @@ from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD, DIALOG_MODEL
from processing.tools import dataobjects
pluginPath = os.path.dirname(__file__)
WIDGET, BASE = uic.loadUiType(
os.path.join(pluginPath, 'fieldsmappingpanelbase.ui'))
class FieldsMappingPanel(BASE, WIDGET):
def __init__(self, parent=None):
super(FieldsMappingPanel, self).__init__(parent)
self.setupUi(self)
self.addButton.setIcon(QgsApplication.getThemeIcon("/mActionNewAttribute.svg"))
self.deleteButton.setIcon(QgsApplication.getThemeIcon('/mActionDeleteAttribute.svg'))
self.upButton.setIcon(QgsApplication.getThemeIcon('/mActionArrowUp.svg'))
self.downButton.setIcon(QgsApplication.getThemeIcon('/mActionArrowDown.svg'))
self.resetButton.setIcon(QgsApplication.getThemeIcon('/mIconClearText.svg'))
self.configure()
self.layerCombo.setAllowEmptyLayer(True)
self.layerCombo.setFilters(QgsMapLayerProxyModel.VectorLayer)
self.dialogType = None
self.layer = None
def configure(self):
self.model = self.fieldsView.model()
self.fieldsView.setDestinationEditable(True)
def setLayer(self, layer):
if layer is None or self.layer == layer:
return
self.layer = layer
if self.model.rowCount(QModelIndex()) == 0:
self.on_resetButton_clicked()
return
dlg = QMessageBox(self)
dlg.setText(self.tr("Do you want to reset the field mapping?"))
dlg.setStandardButtons(
QMessageBox.StandardButtons(QMessageBox.Yes |
QMessageBox.No))
dlg.setDefaultButton(QMessageBox.No)
if dlg.exec_() == QMessageBox.Yes:
self.on_resetButton_clicked()
def value(self):
# Value is a dict with name, type, length, precision and expression
mapping = self.fieldsView.mapping()
results = []
for f in mapping:
results.append({
'name': f.field.name(),
'type': f.field.type(),
'length': f.field.length(),
'precision': f.field.precision(),
'expression': f.expression,
})
return results
def setValue(self, value):
if type(value) != dict:
return
destinationFields = QgsFields()
expressions = {}
for field_def in value:
f = QgsField(field_def.get('name'),
field_def.get('type', QVariant.Invalid),
field_def.get(QVariant.typeToName(field_def.get('type', QVariant.Invalid))),
field_def.get('length', 0),
field_def.get('precision', 0))
try:
expressions[f.name()] = field_def['expressions']
except AttributeError:
pass
destinationFields.append(f)
if len(destinationFields):
self.fieldsView.setDestinationFields(destinationFields, expressions)
@pyqtSlot(bool, name='on_addButton_clicked')
def on_addButton_clicked(self, checked=False):
rowCount = self.model.rowCount(QModelIndex())
self.model.appendField(QgsField('new_field'))
index = self.model.index(rowCount, 0)
self.fieldsView.selectionModel().select(
index,
QItemSelectionModel.SelectionFlags(
QItemSelectionModel.Clear |
QItemSelectionModel.Select |
QItemSelectionModel.Current |
QItemSelectionModel.Rows))
self.fieldsView.scrollTo(index)
@pyqtSlot(bool, name='on_deleteButton_clicked')
def on_deleteButton_clicked(self, checked=False):
self.fieldsView.removeSelectedFields()
@pyqtSlot(bool, name='on_upButton_clicked')
def on_upButton_clicked(self, checked=False):
self.fieldsView.moveSelectedFieldsUp()
@pyqtSlot(bool, name='on_downButton_clicked')
def on_downButton_clicked(self, checked=False):
self.fieldsView.moveSelectedFieldsDown()
@pyqtSlot(bool, name='on_resetButton_clicked')
def on_resetButton_clicked(self, checked=False):
"""Load fields from layer"""
if self.layer:
self.fieldsView.setSourceFields(self.layer.fields())
self.fieldsView.setDestinationFields(self.layer.fields())
@pyqtSlot(bool, name='on_loadLayerFieldsButton_clicked')
def on_loadLayerFieldsButton_clicked(self, checked=False):
layer = self.layerCombo.currentLayer()
if layer is None:
return
self.fieldsView.setSourceFields(layer.fields())
self.fieldsView.setDestinationFields(layer.fields())
class FieldsMappingWidgetWrapper(WidgetWrapper):
def __init__(self, *args, **kwargs):
super(FieldsMappingWidgetWrapper, self).__init__(*args, **kwargs)
self._layer = None
def createPanel(self):
return FieldsMappingPanel()
def createWidget(self):
self.panel = self.createPanel()
self.panel.dialogType = self.dialogType
if self.dialogType == DIALOG_MODELER:
self.combobox = QComboBox()
self.combobox.addItem(QCoreApplication.translate('Processing', '[Preconfigure]'), None)
#fieldsMappingInputs = self.dialog.getAvailableValuesOfType(FieldsMapper.ParameterFieldsMapping)
#for input in fieldsMappingInputs:
# self.combobox.addItem(self.dialog.resolveValueDescription(input), input)
def updatePanelEnabledState():
if self.combobox.currentData() is None:
self.panel.setEnabled(True)
else:
self.panel.setEnabled(False)
self.combobox.currentIndexChanged.connect(updatePanelEnabledState)
widget = QWidget()
widget.setLayout(QVBoxLayout())
widget.layout().addWidget(self.combobox)
widget.layout().addWidget(self.panel)
return widget
else:
return self.panel
def postInitialize(self, wrappers):
for wrapper in wrappers:
if wrapper.parameterDefinition().name() == self.parameterDefinition().parentLayerParameterName():
if wrapper.parameterValue():
self.setLayer(wrapper.parameterValue())
wrapper.widgetValueHasChanged.connect(self.parentLayerChanged)
break
# remove exiting spacers to get FieldsMappingPanel fully expanded
if self.dialogType in (DIALOG_STANDARD, DIALOG_MODELER):
@ -225,34 +68,3 @@ class FieldsMappingWidgetWrapper(WidgetWrapper):
spacer = layout.itemAt(layout.count() - 1)
if isinstance(spacer, QSpacerItem):
layout.removeItem(spacer)
def parentLayerChanged(self, layer=None):
self.setLayer(self.sender().widgetValue())
def setLayer(self, layer):
context = dataobjects.createContext()
if layer == self._layer:
return
if isinstance(layer, QgsProcessingFeatureSourceDefinition):
layer, ok = layer.source.valueAsString(context.expressionContext())
if isinstance(layer, str):
layer = QgsProcessingUtils.mapLayerFromString(layer, context)
if not isinstance(layer, QgsVectorLayer):
layer = None
self._layer = layer
self.panel.setLayer(self._layer)
def linkedVectorLayer(self):
return self._layer
def setValue(self, value):
self.panel.setValue(value)
def value(self):
if self.dialogType == DIALOG_MODELER:
if self.combobox.currentData() is None:
return self.panel.value()
else:
return self.comboValue(combobox=self.combobox)
else:
return self.panel.value()

View File

@ -79,10 +79,6 @@ QgsRefactorFieldsAlgorithm *QgsRefactorFieldsAlgorithm::createInstance() const
void QgsRefactorFieldsAlgorithm::initParameters( const QVariantMap & )
{
std::unique_ptr< QgsProcessingParameterFieldMapping > param = qgis::make_unique< QgsProcessingParameterFieldMapping> ( QStringLiteral( "FIELDS_MAPPING" ), QObject::tr( "Fields mapping" ), QStringLiteral( "INPUT" ) );
QVariantMap metadata;
metadata.insert( QStringLiteral( "widget_wrapper" ), QStringLiteral( "processing.algs.qgis.ui.FieldsMappingPanel.FieldsMappingWidgetWrapper" ) );
param->setMetadata( metadata );
addParameter( param.release() );
}

View File

@ -270,6 +270,7 @@ SET(QGIS_GUI_SRCS
processing/qgsprocessingconfigurationwidgets.cpp
processing/qgsprocessingenummodelerwidget.cpp
processing/qgsprocessingfeaturesourceoptionswidget.cpp
processing/qgsprocessingfieldmapwidgetwrapper.cpp
processing/qgsprocessingguiregistry.cpp
processing/qgsprocessingmaplayercombobox.cpp
processing/qgsprocessingmatrixmodelerwidget.cpp
@ -991,6 +992,7 @@ SET(QGIS_GUI_HDRS
processing/qgsprocessingconfigurationwidgets.h
processing/qgsprocessingenummodelerwidget.h
processing/qgsprocessingfeaturesourceoptionswidget.h
processing/qgsprocessingfieldmapwidgetwrapper.h
processing/qgsprocessinggui.h
processing/qgsprocessingguiregistry.h
processing/qgsprocessingmaplayercombobox.h

View File

@ -0,0 +1,315 @@
/***************************************************************************
qgsprocessingfieldmapwidgetwrapper.cpp
---------------------
Date : June 2020
Copyright : (C) 2020 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 "qgsprocessingfieldmapwidgetwrapper.h"
#include <QBoxLayout>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QStandardItemModel>
#include <QToolButton>
#include "qgspanelwidget.h"
#include "qgsprocessingcontext.h"
#include "qgsvectortilewriter.h"
#include "qgsprocessingparameterfieldmap.h"
/// @cond private
//
// QgsProcessingFieldMapPanelWidget
//
QgsProcessingFieldMapPanelWidget::QgsProcessingFieldMapPanelWidget( QWidget *parent )
: QgsPanelWidget( parent )
{
setupUi( this );
mModel = mFieldsView->model();
mFieldsView->setDestinationEditable( true );
mLayerCombo->setAllowEmptyLayer( true );
mLayerCombo->setFilters( QgsMapLayerProxyModel::VectorLayer );
connect( mResetButton, &QPushButton::clicked, this, &QgsProcessingFieldMapPanelWidget::loadFieldsFromLayer );
connect( mAddButton, &QPushButton::clicked, this, &QgsProcessingFieldMapPanelWidget::addField );
connect( mDeleteButton, &QPushButton::clicked, mFieldsView, &QgsFieldMappingWidget::removeSelectedFields );
connect( mUpButton, &QPushButton::clicked, mFieldsView, &QgsFieldMappingWidget::moveSelectedFieldsUp );
connect( mDownButton, &QPushButton::clicked, mFieldsView, &QgsFieldMappingWidget::moveSelectedFieldsDown );
connect( mLoadLayerFieldsButton, &QPushButton::clicked, this, &QgsProcessingFieldMapPanelWidget::loadLayerFields );
connect( mFieldsView, &QgsFieldMappingWidget::changed, this, [ = ]
{
if ( !mBlockChangedSignal )
{
emit changed();
}
} );
}
void QgsProcessingFieldMapPanelWidget::setLayer( QgsVectorLayer *layer )
{
if ( layer == mLayer )
return;
mLayer = layer;
if ( mModel->rowCount() == 0 )
{
loadFieldsFromLayer();
return;
}
QMessageBox dlg( this );
dlg.setText( tr( "Do you want to reset the field mapping?" ) );
dlg.setStandardButtons(
QMessageBox::StandardButtons( QMessageBox::Yes |
QMessageBox::No ) );
dlg.setDefaultButton( QMessageBox::No );
if ( dlg.exec() == QMessageBox::Yes )
{
loadFieldsFromLayer();
}
}
QgsVectorLayer *QgsProcessingFieldMapPanelWidget::layer()
{
return mLayer;
}
QVariant QgsProcessingFieldMapPanelWidget::value() const
{
const QList<QgsFieldMappingModel::Field> mapping = mFieldsView->mapping();
QVariantList results;
results.reserve( mapping.size() );
for ( const QgsFieldMappingModel::Field &field : mapping )
{
QVariantMap def;
def.insert( QStringLiteral( "name" ), field.field.name() );
def.insert( QStringLiteral( "type" ), static_cast< int >( field.field.type() ) );
def.insert( QStringLiteral( "length" ), field.field.length() );
def.insert( QStringLiteral( "precision" ), field.field.precision() );
def.insert( QStringLiteral( "expression" ), field.expression );
results.append( def );
}
return results;
}
void QgsProcessingFieldMapPanelWidget::setValue( const QVariant &value )
{
if ( value.type() != QVariant::List )
return;
QgsFields destinationFields;
QMap<QString, QString> expressions;
const QVariantList fields = value.toList();
for ( const QVariant &field : fields )
{
const QVariantMap map = field.toMap();
QgsField f( map.value( QStringLiteral( "name" ) ).toString(),
static_cast< QVariant::Type >( map.value( QStringLiteral( "type" ), QVariant::Invalid ).toInt() ),
QVariant::typeToName( static_cast< QVariant::Type >( map.value( QStringLiteral( "type" ), QVariant::Invalid ).toInt() ) ),
map.value( QStringLiteral( "length" ), 0 ).toInt(),
map.value( QStringLiteral( "precision" ), 0 ).toInt() );
if ( !map.value( QStringLiteral( "expression" ) ).toString().isEmpty() )
{
expressions.insert( f.name(), map.value( QStringLiteral( "expression" ) ).toString() );
}
destinationFields.append( f );
}
mBlockChangedSignal = true;
if ( destinationFields.size() > 0 )
mFieldsView->setDestinationFields( destinationFields, expressions );
mBlockChangedSignal = false;
emit changed();
}
void QgsProcessingFieldMapPanelWidget::loadFieldsFromLayer()
{
if ( mLayer )
{
mFieldsView->setSourceFields( mLayer->fields() );
mFieldsView->setDestinationFields( mLayer->fields() );
}
}
void QgsProcessingFieldMapPanelWidget::addField()
{
const int rowCount = mModel->rowCount();
mModel->appendField( QgsField( QStringLiteral( "new_field" ) ) );
QModelIndex index = mModel->index( rowCount, 0 );
mFieldsView->selectionModel()->select(
index,
QItemSelectionModel::SelectionFlags(
QItemSelectionModel::Clear |
QItemSelectionModel::Select |
QItemSelectionModel::Current |
QItemSelectionModel::Rows ) );
mFieldsView->scrollTo( index );
}
void QgsProcessingFieldMapPanelWidget::loadLayerFields()
{
if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( mLayerCombo->currentLayer() ) )
{
mFieldsView->setSourceFields( vl->fields() );
mFieldsView->setDestinationFields( vl->fields() );
}
}
//
// QgsProcessingFieldMapWidgetWrapper
//
QgsProcessingFieldMapWidgetWrapper::QgsProcessingFieldMapWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type, QWidget *parent )
: QgsAbstractProcessingParameterWidgetWrapper( parameter, type, parent )
{
}
QString QgsProcessingFieldMapWidgetWrapper::parameterType() const
{
return QgsProcessingParameterFieldMapping::typeName();
}
QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingFieldMapWidgetWrapper::createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type )
{
return new QgsProcessingFieldMapWidgetWrapper( parameter, type );
}
QWidget *QgsProcessingFieldMapWidgetWrapper::createWidget()
{
mPanel = new QgsProcessingFieldMapPanelWidget( nullptr );
mPanel->setToolTip( parameterDefinition()->toolTip() );
connect( mPanel, &QgsProcessingFieldMapPanelWidget::changed, this, [ = ]
{
emit widgetValueHasChanged( this );
} );
return mPanel;
}
void QgsProcessingFieldMapWidgetWrapper::postInitialize( const QList<QgsAbstractProcessingParameterWidgetWrapper *> &wrappers )
{
QgsAbstractProcessingParameterWidgetWrapper::postInitialize( wrappers );
switch ( type() )
{
case QgsProcessingGui::Standard:
case QgsProcessingGui::Batch:
{
for ( const QgsAbstractProcessingParameterWidgetWrapper *wrapper : wrappers )
{
if ( wrapper->parameterDefinition()->name() == static_cast< const QgsProcessingParameterFieldMapping * >( parameterDefinition() )->parentLayerParameterName() )
{
setParentLayerWrapperValue( wrapper );
connect( wrapper, &QgsAbstractProcessingParameterWidgetWrapper::widgetValueHasChanged, this, [ = ]
{
setParentLayerWrapperValue( wrapper );
} );
break;
}
}
break;
}
case QgsProcessingGui::Modeler:
break;
}
}
void QgsProcessingFieldMapWidgetWrapper::setParentLayerWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *parentWrapper )
{
// evaluate value to layer
QgsProcessingContext *context = nullptr;
std::unique_ptr< QgsProcessingContext > tmpContext;
if ( mProcessingContextGenerator )
context = mProcessingContextGenerator->processingContext();
if ( !context )
{
tmpContext = qgis::make_unique< QgsProcessingContext >();
context = tmpContext.get();
}
QgsVectorLayer *layer = QgsProcessingParameters::parameterAsVectorLayer( parentWrapper->parameterDefinition(), parentWrapper->parameterValue(), *context );
if ( !layer )
{
if ( mPanel )
mPanel->setLayer( nullptr );
return;
}
// need to grab ownership of layer if required - otherwise layer may be deleted when context
// goes out of scope
std::unique_ptr< QgsMapLayer > ownedLayer( context->takeResultLayer( layer->id() ) );
if ( ownedLayer && ownedLayer->type() == QgsMapLayerType::VectorLayer )
{
mParentLayer.reset( qobject_cast< QgsVectorLayer * >( ownedLayer.release() ) );
layer = mParentLayer.get();
}
else
{
// don't need ownership of this layer - it wasn't owned by context (so e.g. is owned by the project)
}
if ( mPanel )
mPanel->setLayer( layer );
}
void QgsProcessingFieldMapWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext & )
{
if ( mPanel )
mPanel->setValue( value );
}
QVariant QgsProcessingFieldMapWidgetWrapper::widgetValue() const
{
return mPanel ? mPanel->value() : QVariant();
}
QStringList QgsProcessingFieldMapWidgetWrapper::compatibleParameterTypes() const
{
return QStringList()
<< QgsProcessingParameterFieldMapping::typeName();
}
QStringList QgsProcessingFieldMapWidgetWrapper::compatibleOutputTypes() const
{
return QStringList();
}
QString QgsProcessingFieldMapWidgetWrapper::modelerExpressionFormatString() const
{
return tr( "an array of map items, each containing a 'name', 'type' and 'expression' values (and optional 'length' and 'precision' values)." );
}
const QgsVectorLayer *QgsProcessingFieldMapWidgetWrapper::linkedVectorLayer() const
{
if ( mPanel && mPanel->layer() )
return mPanel->layer();
return QgsAbstractProcessingParameterWidgetWrapper::linkedVectorLayer();
}
/// @endcond

View File

@ -0,0 +1,101 @@
/***************************************************************************
qgsprocessingfieldmapwidgetwrapper.h
---------------------
Date : June 2020
Copyright : (C) 2020 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 QGSPROCESSINGFIELDMAPWIDGETWRAPPER_H
#define QGSPROCESSINGFIELDMAPWIDGETWRAPPER_H
#define SIP_NO_FILE
#include "qgsprocessingwidgetwrapper.h"
#include "qgsprocessingmultipleselectiondialog.h"
#include "ui_qgsprocessingfieldsmappingpanelbase.h"
class QLineEdit;
class QToolButton;
/// @cond PRIVATE
class GUI_EXPORT QgsProcessingFieldMapPanelWidget : public QgsPanelWidget, private Ui::QgsProcessingFieldMapPanelBase
{
Q_OBJECT
public:
QgsProcessingFieldMapPanelWidget( QWidget *parent = nullptr );
void setLayer( QgsVectorLayer *layer );
QgsVectorLayer *layer();
QVariant value() const;
void setValue( const QVariant &value );
signals:
void changed();
private slots:
void loadFieldsFromLayer();
void addField();
void loadLayerFields();
private:
QgsFieldMappingModel *mModel = nullptr;
QgsVectorLayer *mLayer = nullptr;
bool mBlockChangedSignal = false;
};
class GUI_EXPORT QgsProcessingFieldMapWidgetWrapper : public QgsAbstractProcessingParameterWidgetWrapper, public QgsProcessingParameterWidgetFactoryInterface
{
Q_OBJECT
public:
QgsProcessingFieldMapWidgetWrapper( const QgsProcessingParameterDefinition *parameter = nullptr,
QgsProcessingGui::WidgetType type = QgsProcessingGui::Standard, QWidget *parent = nullptr );
// QgsProcessingParameterWidgetFactoryInterface
QString parameterType() const override;
QgsAbstractProcessingParameterWidgetWrapper *createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type ) override SIP_FACTORY;
// QgsProcessingParameterWidgetWrapper interface
QWidget *createWidget() override SIP_FACTORY;
void postInitialize( const QList< QgsAbstractProcessingParameterWidgetWrapper * > &wrappers ) override;
public slots:
void setParentLayerWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *parentWrapper );
protected:
void setWidgetValue( const QVariant &value, QgsProcessingContext &context ) override;
QVariant widgetValue() const override;
QStringList compatibleParameterTypes() const override;
QStringList compatibleOutputTypes() const override;
QString modelerExpressionFormatString() const override;
const QgsVectorLayer *linkedVectorLayer() const override;
private:
QgsProcessingFieldMapPanelWidget *mPanel = nullptr;
std::unique_ptr< QgsVectorLayer > mParentLayer;
friend class TestProcessingGui;
};
/// @endcond
#endif // QGSPROCESSINGFIELDMAPWIDGETWRAPPER_H

View File

@ -19,6 +19,7 @@
#include "qgsprocessingalgorithmconfigurationwidget.h"
#include "qgsprocessingconfigurationwidgets.h"
#include "qgsprocessingvectortilewriterlayerswidgetwrapper.h"
#include "qgsprocessingfieldmapwidgetwrapper.h"
#include "qgsprocessingwidgetwrapperimpl.h"
#include "qgsprocessingparameters.h"
#include "qgis.h"
@ -66,6 +67,7 @@ QgsProcessingGuiRegistry::QgsProcessingGuiRegistry()
addParameterWidgetFactory( new QgsProcessingRasterDestinationWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingFileDestinationWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingFolderDestinationWidgetWrapper() );
addParameterWidgetFactory( new QgsProcessingFieldMapWidgetWrapper() );
}
QgsProcessingGuiRegistry::~QgsProcessingGuiRegistry()

View File

@ -44,6 +44,10 @@ QgsFieldMappingWidget::QgsFieldMappingWidget( QWidget *parent,
// Make sure columns are updated when rows are added
connect( mModel, &QgsFieldMappingModel::rowsInserted, this, [ = ] { updateColumns(); } );
connect( mModel, &QgsFieldMappingModel::modelReset, this, [ = ] { updateColumns(); } );
connect( mModel, &QgsFieldMappingModel::dataChanged, this, &QgsFieldMappingWidget::changed );
connect( mModel, &QgsFieldMappingModel::rowsInserted, this, &QgsFieldMappingWidget::changed );
connect( mModel, &QgsFieldMappingModel::rowsRemoved, this, &QgsFieldMappingWidget::changed );
connect( mModel, &QgsFieldMappingModel::modelReset, this, &QgsFieldMappingWidget::changed );
}
void QgsFieldMappingWidget::setDestinationEditable( bool editable )

View File

@ -96,6 +96,13 @@ class GUI_EXPORT QgsFieldMappingWidget : public QgsPanelWidget, private Ui::QgsF
*/
void scrollTo( const QModelIndex &index ) const;
signals:
/**
*Emitted when the fields defined in the widget are changed.
*/
void changed();
public slots:
//! Appends a new \a field to the model, with an optional \a expression

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<class>QgsProcessingFieldMapPanelBase</class>
<widget class="QWidget" name="QgsProcessingFieldMapPanelBase">
<property name="geometry">
<rect>
<x>0</x>
@ -35,7 +35,7 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QgsFieldMappingWidget" name="fieldsView" native="true">
<widget class="QgsFieldMappingWidget" name="mFieldsView" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -47,53 +47,73 @@
<item>
<layout class="QVBoxLayout" name="buttonLayout">
<item>
<widget class="QToolButton" name="addButton">
<widget class="QToolButton" name="mAddButton">
<property name="toolTip">
<string>Add new field</string>
</property>
<property name="text">
<string>add</string>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionNewAttribute.svg</normaloff>:/images/themes/default/mActionNewAttribute.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="deleteButton">
<widget class="QToolButton" name="mDeleteButton">
<property name="toolTip">
<string>Delete selected field</string>
</property>
<property name="text">
<string>delete</string>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionDeleteAttribute.svg</normaloff>:/images/themes/default/mActionDeleteAttribute.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="upButton">
<widget class="QToolButton" name="mUpButton">
<property name="toolTip">
<string>Move selected field up</string>
</property>
<property name="text">
<string>up</string>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionArrowUp.svg</normaloff>:/images/themes/default/mActionArrowUp.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="downButton">
<widget class="QToolButton" name="mDownButton">
<property name="toolTip">
<string>Move selected field down</string>
</property>
<property name="text">
<string>down</string>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionArrowDown.svg</normaloff>:/images/themes/default/mActionArrowDown.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="resetButton">
<widget class="QToolButton" name="mResetButton">
<property name="toolTip">
<string>Reset all fields</string>
</property>
<property name="text">
<string>reset</string>
</property>
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mIconClearText.svg</normaloff>:/images/themes/default/mIconClearText.svg</iconset>
</property>
</widget>
</item>
<item>
@ -123,7 +143,7 @@
</widget>
</item>
<item>
<widget class="QgsMapLayerComboBox" name="layerCombo">
<widget class="QgsMapLayerComboBox" name="mLayerCombo">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -133,7 +153,7 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="loadLayerFieldsButton">
<widget class="QPushButton" name="mLoadLayerFieldsButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -156,16 +176,18 @@
<customwidget>
<class>QgsMapLayerComboBox</class>
<extends>QComboBox</extends>
<header>qgis.gui</header>
<header>qgsmaplayercombobox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsFieldMappingWidget</class>
<extends>QWidget</extends>
<header>qgis.gui</header>
<header>qgsfieldmappingwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<resources>
<include location="../../../images/images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -83,6 +83,8 @@
#include "qgsmodelgraphicsscene.h"
#include "qgsmodelgraphicsview.h"
#include "qgsmodelcomponentgraphicitem.h"
#include "qgsprocessingfieldmapwidgetwrapper.h"
#include "qgsprocessingparameterfieldmap.h"
class TestParamType : public QgsProcessingParameterDefinition
{
@ -226,6 +228,8 @@ class TestProcessingGui : public QObject
void testProviderConnectionWrapper();
void testDatabaseSchemaWrapper();
void testDatabaseTableWrapper();
void testFieldMapWidget();
void testFieldMapWrapper();
void testOutputDefinitionWidget();
void testOutputDefinitionWidgetVectorOut();
void testOutputDefinitionWidgetRasterOut();
@ -7184,6 +7188,170 @@ void TestProcessingGui::testDatabaseTableWrapper()
#endif
}
void TestProcessingGui::testFieldMapWidget()
{
QgsProcessingFieldMapPanelWidget widget;
QVariantMap map;
map.insert( QStringLiteral( "name" ), QStringLiteral( "n" ) );
map.insert( QStringLiteral( "type" ), static_cast< int >( QVariant::Double ) );
map.insert( QStringLiteral( "length" ), 8 );
map.insert( QStringLiteral( "precision" ), 5 );
QVariantMap map2;
map2.insert( QStringLiteral( "name" ), QStringLiteral( "n2" ) );
map2.insert( QStringLiteral( "type" ), static_cast< int >( QVariant::String ) );
map2.insert( QStringLiteral( "expression" ), QStringLiteral( "'abc' || \"def\"" ) );
QSignalSpy spy( &widget, &QgsProcessingFieldMapPanelWidget::changed );
widget.setValue( QVariantList() << map << map2 );
QCOMPARE( spy.size(), 1 );
QCOMPARE( widget.value().toList().size(), 2 );
QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "type" ) ).toInt(), static_cast< int >( QVariant::Double ) );
QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "length" ) ).toInt(), 8 );
QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "precision" ) ).toInt(), 5 );
QCOMPARE( widget.value().toList().at( 0 ).toMap().value( QStringLiteral( "expression" ) ).toString(), QString() );
QCOMPARE( widget.value().toList().at( 1 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
QCOMPARE( widget.value().toList().at( 1 ).toMap().value( QStringLiteral( "type" ) ).toInt(), static_cast< int >( QVariant::String ) );
QCOMPARE( widget.value().toList().at( 1 ).toMap().value( QStringLiteral( "expression" ) ).toString(), QStringLiteral( "'abc' || \"def\"" ) );
}
void TestProcessingGui::testFieldMapWrapper()
{
const QgsProcessingAlgorithm *centroidAlg = QgsApplication::processingRegistry()->algorithmById( QStringLiteral( "native:centroids" ) );
const QgsProcessingParameterDefinition *layerDef = centroidAlg->parameterDefinition( QStringLiteral( "INPUT" ) );
auto testWrapper = [layerDef]( QgsProcessingGui::WidgetType type )
{
QgsProcessingParameterFieldMapping param( QStringLiteral( "mapping" ), QStringLiteral( "mapping" ) );
QgsProcessingFieldMapWidgetWrapper wrapper( &param, type );
QgsProcessingContext context;
QWidget *w = wrapper.createWrappedWidget( context );
QVariantMap map;
map.insert( QStringLiteral( "name" ), QStringLiteral( "n" ) );
map.insert( QStringLiteral( "type" ), static_cast< int >( QVariant::Double ) );
map.insert( QStringLiteral( "length" ), 8 );
map.insert( QStringLiteral( "precision" ), 5 );
QVariantMap map2;
map2.insert( QStringLiteral( "name" ), QStringLiteral( "n2" ) );
map2.insert( QStringLiteral( "type" ), static_cast< int >( QVariant::String ) );
map2.insert( QStringLiteral( "expression" ), QStringLiteral( "'abc' || \"def\"" ) );
QSignalSpy spy( &wrapper, &QgsProcessingFieldMapWidgetWrapper::widgetValueHasChanged );
wrapper.setWidgetValue( QVariantList() << map << map2, context );
QCOMPARE( spy.count(), 1 );
QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "type" ) ).toInt(), static_cast< int >( QVariant::Double ) );
QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "length" ) ).toInt(), 8 );
QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "precision" ) ).toInt(), 5 );
QCOMPARE( wrapper.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "expression" ) ).toString(), QString() );
QCOMPARE( wrapper.widgetValue().toList().at( 1 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
QCOMPARE( wrapper.widgetValue().toList().at( 1 ).toMap().value( QStringLiteral( "type" ) ).toInt(), static_cast< int >( QVariant::String ) );
QCOMPARE( wrapper.widgetValue().toList().at( 1 ).toMap().value( QStringLiteral( "expression" ) ).toString(), QStringLiteral( "'abc' || \"def\"" ) );
QCOMPARE( static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper.wrappedWidget() )->value().toList().count(), 2 );
QCOMPARE( static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper.wrappedWidget() )->value().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
QCOMPARE( static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper.wrappedWidget() )->value().toList().at( 1 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
wrapper.setWidgetValue( QVariantList() << map, context );
QCOMPARE( spy.count(), 2 );
QCOMPARE( wrapper.widgetValue().toList().size(), 1 );
QCOMPARE( static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper.wrappedWidget() )->value().toList().size(), 1 );
QLabel *l = wrapper.createWrappedLabel();
if ( wrapper.type() != QgsProcessingGui::Batch )
{
QVERIFY( l );
QCOMPARE( l->text(), QStringLiteral( "mapping" ) );
QCOMPARE( l->toolTip(), param.toolTip() );
delete l;
}
else
{
QVERIFY( !l );
}
// check signal
static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper.wrappedWidget() )->setValue( QVariantList() << map << map2 );
QCOMPARE( spy.count(), 3 );
delete w;
// with layer
param.setParentLayerParameterName( QStringLiteral( "other" ) );
QgsProcessingFieldMapWidgetWrapper wrapper2( &param, type );
w = wrapper2.createWrappedWidget( context );
QSignalSpy spy2( &wrapper2, &QgsProcessingFieldMapWidgetWrapper::widgetValueHasChanged );
wrapper2.setWidgetValue( QVariantList() << map, context );
QCOMPARE( spy2.count(), 1 );
QCOMPARE( wrapper2.widgetValue().toList().size(), 1 );
QCOMPARE( wrapper2.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
QCOMPARE( static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper2.wrappedWidget() )->value().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n" ) );
wrapper2.setWidgetValue( QVariantList() << map2, context );
QCOMPARE( spy2.count(), 2 );
QCOMPARE( wrapper2.widgetValue().toList().size(), 1 );
QCOMPARE( wrapper2.widgetValue().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
QCOMPARE( static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper2.wrappedWidget() )->value().toList().at( 0 ).toMap().value( QStringLiteral( "name" ) ).toString(), QStringLiteral( "n2" ) );
static_cast< QgsProcessingFieldMapPanelWidget * >( wrapper2.wrappedWidget() )->setValue( QVariantList() << map );
QCOMPARE( spy2.count(), 3 );
TestLayerWrapper layerWrapper( layerDef );
QgsProject p;
QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
p.addMapLayer( vl );
QVERIFY( !wrapper2.mPanel->layer() );
layerWrapper.setWidgetValue( QVariant::fromValue( vl ), context );
wrapper2.setParentLayerWrapperValue( &layerWrapper );
QCOMPARE( wrapper2.mPanel->layer(), vl );
// should not be owned by wrapper
QVERIFY( !wrapper2.mParentLayer.get() );
layerWrapper.setWidgetValue( QVariant(), context );
wrapper2.setParentLayerWrapperValue( &layerWrapper );
QVERIFY( !wrapper2.mPanel->layer() );
layerWrapper.setWidgetValue( vl->id(), context );
wrapper2.setParentLayerWrapperValue( &layerWrapper );
QVERIFY( !wrapper2.mPanel->layer() );
QVERIFY( !wrapper2.mParentLayer.get() );
// with project layer
context.setProject( &p );
TestProcessingContextGenerator generator( context );
wrapper2.registerProcessingContextGenerator( &generator );
layerWrapper.setWidgetValue( vl->id(), context );
wrapper2.setParentLayerWrapperValue( &layerWrapper );
QCOMPARE( wrapper2.mPanel->layer(), vl );
QVERIFY( !wrapper2.mParentLayer.get() );
// non-project layer
QString pointFileName = TEST_DATA_DIR + QStringLiteral( "/points.shp" );
layerWrapper.setWidgetValue( pointFileName, context );
wrapper2.setParentLayerWrapperValue( &layerWrapper );
QCOMPARE( wrapper2.mPanel->layer()->publicSource(), pointFileName );
// must be owned by wrapper, or layer may be deleted while still required by wrapper
QCOMPARE( wrapper2.mParentLayer->publicSource(), pointFileName );
};
// standard wrapper
testWrapper( QgsProcessingGui::Standard );
// batch wrapper
testWrapper( QgsProcessingGui::Batch );
// modeler wrapper
testWrapper( QgsProcessingGui::Modeler );
}
void TestProcessingGui::testOutputDefinitionWidget()
{
QgsProcessingParameterFeatureSink sink( QStringLiteral( "test" ) );