diff --git a/python/core/processing/qgsprocessingoutputs.sip b/python/core/processing/qgsprocessingoutputs.sip
index 44a26a742433..2cb435af2296 100644
--- a/python/core/processing/qgsprocessingoutputs.sip
+++ b/python/core/processing/qgsprocessingoutputs.sip
@@ -40,6 +40,8 @@ class QgsProcessingOutputDefinition
sipType = sipType_QgsProcessingOutputString;
else if ( sipCpp->type() == QgsProcessingOutputFolder::typeName() )
sipType = sipType_QgsProcessingOutputFolder;
+ else
+ sipType = nullptr;
%End
public:
diff --git a/python/core/processing/qgsprocessingparameters.sip b/python/core/processing/qgsprocessingparameters.sip
index 29cec7314f85..e0bf143e8959 100644
--- a/python/core/processing/qgsprocessingparameters.sip
+++ b/python/core/processing/qgsprocessingparameters.sip
@@ -191,6 +191,8 @@ class QgsProcessingParameterDefinition
sipType = sipType_QgsProcessingParameterFolderDestination;
else if ( sipCpp->type() == QgsProcessingParameterBand::typeName() )
sipType = sipType_QgsProcessingParameterBand;
+ else
+ sipType = nullptr;
%End
public:
diff --git a/python/plugins/processing/algs/qgis/FieldsMapper.py b/python/plugins/processing/algs/qgis/FieldsMapper.py
index 64c3e4fcf6ec..7bba9b0a3f0b 100644
--- a/python/plugins/processing/algs/qgis/FieldsMapper.py
+++ b/python/plugins/processing/algs/qgis/FieldsMapper.py
@@ -25,79 +25,76 @@
__revision__ = '$Format:%H$'
-from qgis.core import (QgsField,
- QgsFields,
- QgsExpression,
- QgsDistanceArea,
- QgsFeatureSink,
- QgsProject,
- QgsFeature,
- QgsApplication,
- QgsProcessingUtils)
-from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
-from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
-from processing.core.parameters import ParameterTable
-from processing.core.parameters import Parameter
-from processing.core.outputs import OutputVector
-
-
-class FieldsMapper(QgisAlgorithm):
+from qgis.core import (
+ QgsApplication,
+ QgsDistanceArea,
+ QgsExpression,
+ QgsFeature,
+ QgsFeatureSink,
+ QgsField,
+ QgsFields,
+ QgsProcessingException,
+ QgsProcessingParameterDefinition,
+ QgsProcessingUtils,
+ QgsProject,
+)
+
+from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
+
+
+class FieldsMapper(QgisFeatureBasedAlgorithm):
INPUT_LAYER = 'INPUT_LAYER'
FIELDS_MAPPING = 'FIELDS_MAPPING'
OUTPUT_LAYER = 'OUTPUT_LAYER'
- def __init__(self):
- GeoAlgorithm.__init__(self)
- self.mapping = None
-
def group(self):
return self.tr('Vector table tools')
- def __init__(self):
- super().__init__()
-
- def initAlgorithm(self, config=None):
- self.addParameter(ParameterTable(self.INPUT_LAYER,
- self.tr('Input layer'),
- False))
+ def initParameters(self, config=None):
- class ParameterFieldsMapping(Parameter):
+ class ParameterFieldsMapping(QgsProcessingParameterDefinition):
- default_metadata = {
- 'widget_wrapper': 'processing.algs.qgis.ui.FieldsMappingPanel.FieldsMappingWidgetWrapper'
- }
+ def __init__(self, name, description, parentLayerParameterName='INPUT'):
+ super().__init__(name, description)
+ self._parentLayerParameter = parentLayerParameterName
- def __init__(self, name='', description='', parent=None):
- Parameter.__init__(self, name, description)
- self.parent = parent
- self.value = []
+ def type(self):
+ return 'fields_mapping'
- def getValueAsCommandLineParameter(self):
- return '"' + str(self.value) + '"'
-
- def setValue(self, value):
- if value is None:
+ def checkValueIsAcceptable(self, value, context):
+ if not isinstance(value, list):
return False
- if isinstance(value, list):
- self.value = value
- return True
- if isinstance(value, str):
- try:
- self.value = eval(value)
- return True
- except Exception as e:
- # fix_print_with_import
- print(str(e)) # display error in console
+ for field_def in value:
+ if not isinstance(field_def, dict):
+ return False
+ if not field_def.get('name', False):
+ return False
+ if not field_def.get('type', False):
+ return False
+ if not field_def.get('expression', False):
return False
- return False
+ return True
- self.addParameter(ParameterFieldsMapping(self.FIELDS_MAPPING,
- self.tr('Fields mapping'),
- self.INPUT_LAYER))
- self.addOutput(OutputVector(self.OUTPUT_LAYER,
- self.tr('Refactored'),
- base_input=self.INPUT_LAYER))
+ def valueAsPythonString(self, value, context):
+ return str(value)
+
+ def asScriptCode(self):
+ raise NotImplementedError()
+
+ @classmethod
+ def fromScriptCode(cls, name, description, isOptional, definition):
+ raise NotImplementedError()
+
+ def parentLayerParameter(self):
+ return self._parentLayerParameter
+
+ fields_mapping = ParameterFieldsMapping(self.FIELDS_MAPPING,
+ description=self.tr('Fields mapping'))
+ fields_mapping.setMetadata({
+ 'widget_wrapper': 'processing.algs.qgis.ui.FieldsMappingPanel.FieldsMappingWidgetWrapper'
+ })
+ self.addParameter(fields_mapping)
def name(self):
return 'refactorfields'
@@ -105,79 +102,64 @@ def name(self):
def displayName(self):
return self.tr('Refactor fields')
- def processAlgorithm(self, parameters, context, feedback):
- layer = self.getParameterValue(self.INPUT_LAYER)
- mapping = self.getParameterValue(self.FIELDS_MAPPING)
- output = self.getOutputFromName(self.OUTPUT_LAYER)
+ def outputName(self):
+ return self.tr('Refactored')
- layer = QgsProcessingUtils.mapLayerFromString(layer, context)
- fields = QgsFields()
- expressions = []
+ def parameterAsFieldsMapping(self, parameters, name, context):
+ return parameters[name]
+
+ def prepareAlgorithm(self, parameters, context, feedback):
+ source = self.parameterAsSource(parameters, 'INPUT', context)
+ mapping = self.parameterAsFieldsMapping(parameters, self.FIELDS_MAPPING, context)
+
+ self.fields = QgsFields()
+ self.expressions = []
da = QgsDistanceArea()
- da.setSourceCrs(layer.crs())
+ da.setSourceCrs(source.sourceCrs())
da.setEllipsoid(context.project().ellipsoid())
- exp_context = layer.createExpressionContext()
-
for field_def in mapping:
- fields.append(QgsField(field_def['name'],
- field_def['type'],
- field_def['length'],
- field_def['precision']))
-
+ self.fields.append(QgsField(name=field_def['name'],
+ type=field_def['type'],
+ typeName="",
+ len=field_def.get('length', 0),
+ prec=field_def.get('precision', 0)))
expression = QgsExpression(field_def['expression'])
expression.setGeomCalculator(da)
expression.setDistanceUnits(context.project().distanceUnits())
expression.setAreaUnits(context.project().areaUnits())
- expression.prepare(exp_context)
if expression.hasParserError():
- raise GeoAlgorithmExecutionException(
+ raise QgsProcessingException(
self.tr(u'Parser error in expression "{}": {}')
.format(str(expression.expression()),
str(expression.parserErrorString())))
- expressions.append(expression)
-
- writer = output.getVectorWriter(fields, layer.wkbType(), layer.crs(), context)
-
- # Create output vector layer with new attributes
- error_exp = None
- inFeat = QgsFeature()
- outFeat = QgsFeature()
- features = QgsProcessingUtils.getFeatures(layer, context)
- count = QgsProcessingUtils.featureCount(layer, context)
- if count > 0:
- total = 100.0 / count
- for current, inFeat in enumerate(features):
- rownum = current + 1
-
- geometry = inFeat.geometry()
- outFeat.setGeometry(geometry)
-
- attrs = []
- for i in range(0, len(mapping)):
- field_def = mapping[i]
- expression = expressions[i]
- exp_context.setFeature(inFeat)
- exp_context.lastScope().setVariable("row_number", rownum)
- value = expression.evaluate(exp_context)
- if expression.hasEvalError():
- error_exp = expression
- break
-
- attrs.append(value)
- outFeat.setAttributes(attrs)
-
- writer.addFeature(outFeat, QgsFeatureSink.FastInsert)
-
- feedback.setProgress(int(current * total))
- else:
- feedback.setProgress(100)
-
- del writer
-
- if error_exp is not None:
- raise GeoAlgorithmExecutionException(
- self.tr(u'Evaluation error in expression "{}": {}')
- .format(str(error_exp.expression()),
- str(error_exp.parserErrorString())))
+ self.expressions.append(expression)
+ return True
+
+ def outputFields(self, inputFields):
+ return self.fields
+
+ def processAlgorithm(self, parameters, context, feeback):
+ # create an expression context using thead safe processing context
+ self.expr_context = self.createExpressionContext(parameters, context)
+ for expression in self.expressions:
+ expression.prepare(self.expr_context)
+ self._row_number = 0
+ return super().processAlgorithm(parameters, context, feeback)
+
+ def processFeature(self, feature, feedback):
+ attributes = []
+ for expression in self.expressions:
+ self.expr_context.setFeature(feature)
+ self.expr_context.lastScope().setVariable("row_number", self._row_number)
+ value = expression.evaluate(self.expr_context)
+ if expression.hasEvalError():
+ raise QgsProcessingException(
+ self.tr(u'Evaluation error in expression "{}": {}')
+ .format(str(expression.expression()),
+ str(expression.parserErrorString())))
+ attributes.append(value)
+ feature.setAttributes(attributes)
+ self._row_number += 1
+ return feature
diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
index fb062d617d41..6629e62fbe79 100644
--- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
+++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
@@ -160,7 +160,7 @@
# from .SetRasterStyle import SetRasterStyle
# from .SelectByAttributeSum import SelectByAttributeSum
# from .HypsometricCurves import HypsometricCurves
-# from .FieldsMapper import FieldsMapper
+from .FieldsMapper import FieldsMapper
# from .Datasources2Vrt import Datasources2Vrt
# from .OrientedMinimumBoundingBox import OrientedMinimumBoundingBox
# from .DefineProjection import DefineProjection
@@ -231,6 +231,7 @@ def getAlgs(self):
ExtentFromLayer(),
ExtractNodes(),
ExtractSpecificNodes(),
+ FieldsMapper(),
FixedDistanceBuffer(),
FixGeometry(),
GeometryByExpression(),
diff --git a/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py b/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py
index 15bed4f37157..6ec441c3fc7d 100644
--- a/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py
+++ b/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py
@@ -31,17 +31,38 @@
from collections import OrderedDict
from qgis.PyQt import uic
-from qgis.PyQt.QtGui import QBrush
-from qgis.PyQt.QtWidgets import QComboBox, QHeaderView, QLineEdit, QSpacerItem, QMessageBox, QSpinBox, QStyledItemDelegate
-from qgis.PyQt.QtCore import QItemSelectionModel, QAbstractTableModel, QModelIndex, QVariant, Qt, pyqtSlot
-from qgis.core import (QgsExpression,
- QgsProject,
- QgsApplication,
- QgsProcessingUtils)
+from qgis.PyQt.QtCore import (
+ QItemSelectionModel,
+ QAbstractTableModel,
+ QModelIndex,
+ QVariant,
+ Qt,
+ pyqtSlot,
+)
+from qgis.PyQt.QtGui import QBrush
+from qgis.PyQt.QtWidgets import (
+ QComboBox,
+ QHeaderView,
+ QLineEdit,
+ QSpacerItem,
+ QMessageBox,
+ QSpinBox,
+ QStyledItemDelegate,
+)
+
+from qgis.core import (
+ QgsApplication,
+ QgsExpression,
+ QgsProcessingFeatureSourceDefinition,
+ QgsProcessingUtils,
+ QgsProject,
+ QgsVectorLayer,
+)
from qgis.gui import QgsFieldExpressionWidget
from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD, DIALOG_MODELER
+from processing.tools import dataobjects
pluginPath = os.path.dirname(__file__)
WIDGET, BASE = uic.loadUiType(
@@ -51,26 +72,49 @@
class FieldsMappingModel(QAbstractTableModel):
fieldTypes = OrderedDict([
- (QVariant.Int, "Integer"),
+ (QVariant.Date, "Date"),
+ (QVariant.DateTime, "DateTime"),
(QVariant.Double, "Double"),
- (QVariant.String, "String"),
- (QVariant.DateTime, "Date"),
- (QVariant.LongLong, "Double"),
- (QVariant.Date, "Date")])
-
- columns = [
- {'name': 'name', 'type': QVariant.String},
- {'name': 'type', 'type': QVariant.Type},
- {'name': 'length', 'type': QVariant.Int},
- {'name': 'precision', 'type': QVariant.Int},
- # {'name': 'comment', 'type': QVariant.String},
- {'name': 'expression', 'type': QgsExpression}]
+ (QVariant.Int, "Integer"),
+ (QVariant.LongLong, "Integer64"),
+ (QVariant.String, "String")])
def __init__(self, parent=None):
super(FieldsMappingModel, self).__init__(parent)
self._mapping = []
self._errors = []
self._layer = None
+ self.configure()
+
+ def configure(self):
+ self.columns = [{
+ 'name': 'expression',
+ 'type': QgsExpression,
+ 'header': self.tr("Source expression"),
+ 'persistentEditor': True
+ }, {
+ 'name': 'name',
+ 'type': QVariant.String,
+ 'header': self.tr("Field name")
+ }, {
+ 'name': 'type',
+ 'type': QVariant.Type,
+ 'header': self.tr("Type"),
+ 'persistentEditor': True
+ }, {
+ 'name': 'length',
+ 'type': QVariant.Int,
+ 'header': self.tr("Length")
+ }, {
+ 'name': 'precision',
+ 'type': QVariant.Int,
+ 'header': self.tr("Precision")
+ }]
+
+ def columnIndex(self, column_name):
+ for index, column in enumerate(self.columns):
+ if column['name'] == column_name:
+ return index
def mapping(self):
return self._mapping
@@ -78,36 +122,8 @@ def mapping(self):
def setMapping(self, value):
self.beginResetModel()
self._mapping = value
- self.testAllExpressions()
self.endResetModel()
- def testAllExpressions(self):
- self._errors = [None for i in range(len(self._mapping))]
- for row in range(len(self._mapping)):
- self.testExpression(row)
-
- def testExpression(self, row):
- self._errors[row] = None
- field = self._mapping[row]
- exp_context = self.contextGenerator().createExpressionContext()
-
- expression = QgsExpression(field['expression'])
- expression.prepare(exp_context)
- if expression.hasParserError():
- self._errors[row] = expression.parserErrorString()
- return
-
- # test evaluation on the first feature
- if self._layer is None:
- return
- for feature in self._layer.getFeatures():
- exp_context.setFeature(feature)
- exp_context.lastScope().setVariable("row_number", 1)
- expression.evaluate(exp_context)
- if expression.hasEvalError():
- self._errors[row] = expression.evalErrorString()
- break
-
def contextGenerator(self):
if self._layer:
return self._layer
@@ -118,7 +134,6 @@ def layer(self):
def setLayer(self, layer):
self._layer = layer
- self.testAllExpressions()
def columnCount(self, parent=QModelIndex()):
if parent.isValid():
@@ -133,70 +148,45 @@ def rowCount(self, parent=QModelIndex()):
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
- return self.columns[section]['name'].title()
+ return self.columns[section]['header']
if orientation == Qt.Vertical:
return section
def flags(self, index):
- flags = (Qt.ItemIsSelectable |
- Qt.ItemIsEditable |
- Qt.ItemIsEnabled)
-
- return Qt.ItemFlags(flags)
+ return Qt.ItemFlags(Qt.ItemIsSelectable |
+ Qt.ItemIsEditable |
+ Qt.ItemIsEnabled)
def data(self, index, role=Qt.DisplayRole):
- column = index.column()
+ field = self._mapping[index.row()]
+ column_def = self.columns[index.column()]
if role == Qt.DisplayRole:
- field = self._mapping[index.row()]
- column_def = self.columns[column]
value = field[column_def['name']]
-
- fieldType = column_def['type']
- if fieldType == QVariant.Type:
+ if column_def['type'] == QVariant.Type:
if value == QVariant.Invalid:
return ''
return self.fieldTypes[value]
return value
if role == Qt.EditRole:
- field = self._mapping[index.row()]
- column_def = self.columns[column]
- value = field[column_def['name']]
- return value
+ return field[column_def['name']]
if role == Qt.TextAlignmentRole:
- fieldType = self.columns[column]['type']
- if fieldType in [QVariant.Int]:
+ if column_def['type'] in [QVariant.Int]:
hAlign = Qt.AlignRight
else:
hAlign = Qt.AlignLeft
return hAlign + Qt.AlignVCenter
- if role == Qt.ForegroundRole:
- column_def = self.columns[column]
- if column_def['name'] == 'expression':
- brush = QBrush()
- if self._errors[index.row()]:
- brush.setColor(Qt.red)
- else:
- brush.setColor(Qt.black)
- return brush
-
- if role == Qt.ToolTipRole:
- column_def = self.columns[column]
- if column_def['name'] == 'expression':
- return self._errors[index.row()]
-
def setData(self, index, value, role=Qt.EditRole):
+ field = self._mapping[index.row()]
+ column_def = self.columns[index.column()]
+
if role == Qt.EditRole:
- field = self._mapping[index.row()]
- column = index.column()
- column_def = self.columns[column]
field[column_def['name']] = value
- if column_def['name'] == 'expression':
- self.testExpression(index.row())
self.dataChanged.emit(index, index)
+
return True
def insertRows(self, row, count, index=QModelIndex()):
@@ -205,8 +195,6 @@ def insertRows(self, row, count, index=QModelIndex()):
for i in range(count):
field = self.newField()
self._mapping.insert(row + i, field)
- self._errors.insert(row + i, None)
- self.testExpression(row)
self.endInsertRows()
return True
@@ -216,7 +204,6 @@ def removeRows(self, row, count, index=QModelIndex()):
for i in range(row + count - 1, row + 1):
self._mapping.pop(i)
- self._errors.pop(i)
self.endRemoveRows()
return True
@@ -243,79 +230,57 @@ def loadLayerFields(self, layer):
dp = layer.dataProvider()
for field in dp.fields():
self._mapping.append(self.newField(field))
- self.testAllExpressions()
self.endResetModel()
-class FieldDelegate(QStyledItemDelegate):
-
- def __init__(self, parent=None):
- super(FieldDelegate, self).__init__(parent)
+class FieldTypeDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
- column = index.column()
+ editor = QComboBox(parent)
+ for key, text in list(FieldsMappingModel.fieldTypes.items()):
+ editor.addItem(text, key)
+ return editor
+
+ def setEditorData(self, editor, index):
+ if not editor:
+ return
+ value = index.model().data(index, Qt.EditRole)
+ editor.setCurrentIndex(editor.findData(value))
- fieldType = FieldsMappingModel.columns[column]['type']
- if fieldType == QVariant.Type:
- editor = QComboBox(parent)
- for key, text in list(FieldsMappingModel.fieldTypes.items()):
- editor.addItem(text, key)
+ def setModelData(self, editor, model, index):
+ if not editor:
+ return
+ value = editor.currentData()
+ if value is None:
+ value = QVariant.Invalid
+ model.setData(index, value)
- elif fieldType == QgsExpression:
- editor = QgsFieldExpressionWidget(parent)
- editor.setLayer(index.model().layer())
- editor.registerExpressionContextGenerator(index.model().contextGenerator())
- editor.fieldChanged.connect(self.on_expression_fieldChange)
- else:
- editor = QStyledItemDelegate.createEditor(self, parent, option, index)
+class ExpressionDelegate(QStyledItemDelegate):
+ def createEditor(self, parent, option, index):
+ editor = QgsFieldExpressionWidget(parent)
+ editor.setLayer(index.model().layer())
+ editor.registerExpressionContextGenerator(index.model().contextGenerator())
+ editor.fieldChanged.connect(self.on_expression_fieldChange)
editor.setAutoFillBackground(True)
return editor
def setEditorData(self, editor, index):
if not editor:
return
-
- column = index.column()
value = index.model().data(index, Qt.EditRole)
-
- fieldType = FieldsMappingModel.columns[column]['type']
- if fieldType == QVariant.Type:
- editor.setCurrentIndex(editor.findData(value))
-
- elif fieldType == QgsExpression:
- editor.setField(value)
-
- else:
- QStyledItemDelegate.setEditorData(self, editor, index)
+ editor.setField(value)
def setModelData(self, editor, model, index):
if not editor:
return
-
- column = index.column()
-
- fieldType = FieldsMappingModel.columns[column]['type']
- if fieldType == QVariant.Type:
- value = editor.currentData()
- if value is None:
- value = QVariant.Invalid
+ (value, isExpression, isValid) = editor.currentField()
+ if isExpression is True:
model.setData(index, value)
-
- elif fieldType == QgsExpression:
- (value, isExpression, isValid) = editor.currentField()
- if isExpression is True:
- model.setData(index, value)
- else:
- model.setData(index, QgsExpression.quotedColumnRef(value))
-
else:
- QStyledItemDelegate.setModelData(self, editor, model, index)
-
- def updateEditorGeometry(self, editor, option, index):
- editor.setGeometry(option.rect)
+ model.setData(index, QgsExpression.quotedColumnRef(value))
def on_expression_fieldChange(self, fieldName):
self.commitData.emit(self.sender())
@@ -333,27 +298,40 @@ def __init__(self, parent=None):
self.downButton.setIcon(QgsApplication.getThemeIcon('/mActionArrowDown.svg'))
self.resetButton.setIcon(QgsApplication.getThemeIcon('/mIconClearText.svg'))
- self.model = FieldsMappingModel()
- self.fieldsView.setModel(self.model)
+ self.configure()
+ self.model.modelReset.connect(self.on_model_modelReset)
self.model.rowsInserted.connect(self.on_model_rowsInserted)
- self.fieldsView.setItemDelegate(FieldDelegate())
self.updateLayerCombo()
+ def configure(self):
+ self.model = FieldsMappingModel()
+ self.fieldsView.setModel(self.model)
+
+ self.setDelegate('expression', ExpressionDelegate(self))
+ self.setDelegate('type', FieldTypeDelegate(self))
+
+ def setDelegate(self, column_name, delegate):
+ self.fieldsView.setItemDelegateForColumn(
+ self.model.columnIndex(column_name),
+ delegate)
+
def setLayer(self, layer):
self.model.setLayer(layer)
+ if layer is None:
+ return
if self.model.rowCount() == 0:
self.on_resetButton_clicked()
- else:
- dlg = QMessageBox(self)
- dlg.setText("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()
+ return
+ dlg = QMessageBox(self)
+ dlg.setText("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):
return self.model.mapping()
@@ -366,11 +344,13 @@ def on_addButton_clicked(self, checked=False):
rowCount = self.model.rowCount()
self.model.insertRows(rowCount, 1)
index = self.model.index(rowCount, 0)
- self.fieldsView.selectionModel().select(index,
- QItemSelectionModel.SelectionFlags(QItemSelectionModel.Clear |
- QItemSelectionModel.Select |
- QItemSelectionModel.Current |
- QItemSelectionModel.Rows))
+ self.fieldsView.selectionModel().select(
+ index,
+ QItemSelectionModel.SelectionFlags(
+ QItemSelectionModel.Clear |
+ QItemSelectionModel.Select |
+ QItemSelectionModel.Current |
+ QItemSelectionModel.Rows))
self.fieldsView.scrollTo(index)
self.fieldsView.scrollTo(index)
@@ -404,11 +384,13 @@ def on_upButton_clicked(self, checked=False):
self.model.removeRows(row + 1, 1)
- sel.select(self.model.index(row - 1, 0),
- QItemSelectionModel.SelectionFlags(QItemSelectionModel.Clear |
- QItemSelectionModel.Select |
- QItemSelectionModel.Current |
- QItemSelectionModel.Rows))
+ sel.select(
+ self.model.index(row - 1, 0),
+ QItemSelectionModel.SelectionFlags(
+ QItemSelectionModel.Clear |
+ QItemSelectionModel.Select |
+ QItemSelectionModel.Current |
+ QItemSelectionModel.Rows))
@pyqtSlot(bool, name='on_downButton_clicked')
def on_downButton_clicked(self, checked=False):
@@ -430,38 +412,36 @@ def on_downButton_clicked(self, checked=False):
self.model.removeRows(row, 1)
- sel.select(self.model.index(row + 1, 0),
- QItemSelectionModel.SelectionFlags(QItemSelectionModel.Clear |
- QItemSelectionModel.Select |
- QItemSelectionModel.Current |
- QItemSelectionModel.Rows))
+ sel.select(
+ self.model.index(row + 1, 0),
+ QItemSelectionModel.SelectionFlags(
+ QItemSelectionModel.Clear |
+ QItemSelectionModel.Select |
+ QItemSelectionModel.Current |
+ QItemSelectionModel.Rows))
@pyqtSlot(bool, name='on_resetButton_clicked')
def on_resetButton_clicked(self, checked=False):
self.model.loadLayerFields(self.model.layer())
- self.openPersistentEditor(
- self.model.index(0, 0),
- self.model.index(self.model.rowCount() - 1,
- self.model.columnCount() - 1))
- self.resizeColumns()
def resizeColumns(self):
header = self.fieldsView.horizontalHeader()
header.resizeSections(QHeaderView.ResizeToContents)
for section in range(header.count()):
size = header.sectionSize(section)
- fieldType = FieldsMappingModel.columns[section]['type']
+ fieldType = self.model.columns[section]['type']
if fieldType == QgsExpression:
header.resizeSection(section, size + 100)
else:
header.resizeSection(section, size + 20)
- def openPersistentEditor(self, topLeft, bottomRight):
- return
- for row in range(topLeft.row(), bottomRight.row() + 1):
- for column in range(topLeft.column(), bottomRight.column() + 1):
- self.fieldsView.openPersistentEditor(self.model.index(row, column))
- editor = self.fieldsView.indexWidget(self.model.index(row, column))
+ def openPersistentEditors(self, row):
+ for index, column in enumerate(self.model.columns):
+ if 'persistentEditor' in column.keys() and column['persistentEditor']:
+ self.fieldsView.openPersistentEditor(self.model.index(row, index))
+ continue
+
+ editor = self.fieldsView.indexWidget(self.model.index(row, index))
if isinstance(editor, QLineEdit):
editor.deselect()
if isinstance(editor, QSpinBox):
@@ -469,10 +449,14 @@ def openPersistentEditor(self, topLeft, bottomRight):
lineEdit.setAlignment(Qt.AlignRight or Qt.AlignVCenter)
lineEdit.deselect()
+ def on_model_modelReset(self):
+ for row in range(0, self.model.rowCount()):
+ self.openPersistentEditors(row)
+ self.resizeColumns()
+
def on_model_rowsInserted(self, parent, start, end):
- self.openPersistentEditor(
- self.model.index(start, 0),
- self.model.index(end, self.model.columnCount() - 1))
+ for row in range(start, end + 1):
+ self.openPersistentEditors(row)
def updateLayerCombo(self):
layers = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance())
@@ -489,18 +473,19 @@ def on_loadLayerFieldsButton_clicked(self, checked=False):
class FieldsMappingWidgetWrapper(WidgetWrapper):
+ def __init__(self, *args, **kwargs):
+ super(FieldsMappingWidgetWrapper, self).__init__(*args, **kwargs)
+ self._layer = None
+
def createWidget(self):
return FieldsMappingPanel()
def postInitialize(self, wrappers):
for wrapper in wrappers:
- if wrapper.param.name == self.param.parent:
+ if wrapper.param.name() == self.param.parentLayerParameter():
+ self.setLayer(wrapper.value())
wrapper.widgetValueHasChanged.connect(self.parentLayerChanged)
break
- layers = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance())
- if len(layers) > 0:
- # as first item in combobox is already selected
- self.widget.setLayer(layers[0])
# remove exiting spacers to get FieldsMappingPanel fully expanded
if self.dialogType in (DIALOG_STANDARD, DIALOG_MODELER):
@@ -509,8 +494,21 @@ def postInitialize(self, wrappers):
if isinstance(spacer, QSpacerItem):
layout.removeItem(spacer)
- def parentLayerChanged(self):
- self.widget.setLayer(self.sender().value())
+ def parentLayerChanged(self, layer=None):
+ self.setLayer(self.sender().value())
+
+ 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.widget.setLayer(self._layer)
def setValue(self, value):
self.widget.setValue(value)
diff --git a/python/plugins/processing/tests/testdata/expected/refactorfields.gfs b/python/plugins/processing/tests/testdata/expected/refactorfields.gfs
new file mode 100644
index 000000000000..2c17f8b03750
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/refactorfields.gfs
@@ -0,0 +1,32 @@
+
+
+ refactorfields
+ refactorfields
+
+ 6
+ EPSG:4326
+
+ 4
+ 0.00000
+ 9.00000
+ -1.00000
+ 6.00000
+
+
+ Bname
+ Bname
+ String
+ 19
+
+
+ Bintval
+ Bintval
+ Integer
+
+
+ Bfloatval
+ Bfloatval
+ Real
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/refactorfields.gml b/python/plugins/processing/tests/testdata/expected/refactorfields.gml
new file mode 100644
index 000000000000..b3f88b16cc81
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/refactorfields.gml
@@ -0,0 +1,46 @@
+
+
+
+
+ 0-1
+ 96
+
+
+
+
+
+ 2,1 2,2 3,2 3,3 4,3 4,1 2,1
+ 0
+ multipolys.0 - Test
+ 2
+ 0.246
+
+
+
+
+ 7,-1 8,-1 8,3 7,3 7,-17,6 7,5 7,4 8,4 9,5 9,6 7,6
+ 1
+
+
+
+
+ 0,0 0,1 1,1 1,0 0,0
+ 2
+ multipolys.2 - Test
+ 3
+ -0.246
+
+
+
+
+ 3
+ multipolys.3 - Test
+ 4
+ 0
+
+
+
diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
index 2361db61ca37..2a97476caf14 100644
--- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
+++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
@@ -2086,6 +2086,38 @@ tests:
geometry:
precision: 7
+ - algorithm: qgis:refactorfields
+ name: refactor fields
+ params:
+ INPUT:
+ name: multipolys.gml
+ type: vector
+ FIELDS_MAPPING:
+ - expression: '@row_number'
+ name: 'row_number'
+ type: 2
+ length: 0
+ precision: 0
+ - expression: >
+ "fid" || ' - ' || "Bname"
+ name: 'Bname'
+ type: 10
+ length: 30
+ precision: 0
+ - expression: '"Bintval" + 1'
+ name: 'Bintval'
+ type: 2
+ length: 0
+ precision: 0
+ - expression: '"Bfloatval" * 2'
+ name: 'Bfloatval'
+ type: 6
+ length: 0
+ precision: 0
+ results:
+ OUTPUT:
+ name: expected/refactorfields.gml
+ type: vector
- algorithm: native:reprojectlayer
name: reproject vector layer
diff --git a/src/core/processing/qgsprocessingoutputs.h b/src/core/processing/qgsprocessingoutputs.h
index 5157d2c2ca8b..139ba90e6dbc 100644
--- a/src/core/processing/qgsprocessingoutputs.h
+++ b/src/core/processing/qgsprocessingoutputs.h
@@ -55,6 +55,8 @@ class CORE_EXPORT QgsProcessingOutputDefinition
sipType = sipType_QgsProcessingOutputString;
else if ( sipCpp->type() == QgsProcessingOutputFolder::typeName() )
sipType = sipType_QgsProcessingOutputFolder;
+ else
+ sipType = nullptr;
SIP_END
#endif
diff --git a/src/core/processing/qgsprocessingparameters.h b/src/core/processing/qgsprocessingparameters.h
index 663627b6dc7d..aec0eee7ddb9 100644
--- a/src/core/processing/qgsprocessingparameters.h
+++ b/src/core/processing/qgsprocessingparameters.h
@@ -233,6 +233,8 @@ class CORE_EXPORT QgsProcessingParameterDefinition
sipType = sipType_QgsProcessingParameterFolderDestination;
else if ( sipCpp->type() == QgsProcessingParameterBand::typeName() )
sipType = sipType_QgsProcessingParameterBand;
+ else
+ sipType = nullptr;
SIP_END
#endif