[processing] Port refactor fields to new API

This commit is contained in:
arnaud.morvan@camptocamp.com 2017-07-21 19:46:27 +02:00 committed by Matthias Kuhn
parent 2364801634
commit b3a9e46cfe
10 changed files with 404 additions and 305 deletions

View File

@ -40,6 +40,8 @@ class QgsProcessingOutputDefinition
sipType = sipType_QgsProcessingOutputString;
else if ( sipCpp->type() == QgsProcessingOutputFolder::typeName() )
sipType = sipType_QgsProcessingOutputFolder;
else
sipType = nullptr;
%End
public:

View File

@ -191,6 +191,8 @@ class QgsProcessingParameterDefinition
sipType = sipType_QgsProcessingParameterFolderDestination;
else if ( sipCpp->type() == QgsProcessingParameterBand::typeName() )
sipType = sipType_QgsProcessingParameterBand;
else
sipType = nullptr;
%End
public:

View File

@ -25,79 +25,76 @@ __copyright__ = '(C) 2014, Arnaud Morvan'
__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
from qgis.core import (
QgsApplication,
QgsDistanceArea,
QgsExpression,
QgsFeature,
QgsFeatureSink,
QgsField,
QgsFields,
QgsProcessingException,
QgsProcessingParameterDefinition,
QgsProcessingUtils,
QgsProject,
)
from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
class FieldsMapper(QgisAlgorithm):
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 initParameters(self, config=None):
def initAlgorithm(self, config=None):
self.addParameter(ParameterTable(self.INPUT_LAYER,
self.tr('Input layer'),
False))
class ParameterFieldsMapping(QgsProcessingParameterDefinition):
class ParameterFieldsMapping(Parameter):
def __init__(self, name, description, parentLayerParameterName='INPUT'):
super().__init__(name, description)
self._parentLayerParameter = parentLayerParameterName
default_metadata = {
'widget_wrapper': 'processing.algs.qgis.ui.FieldsMappingPanel.FieldsMappingWidgetWrapper'
}
def type(self):
return 'fields_mapping'
def __init__(self, name='', description='', parent=None):
Parameter.__init__(self, name, description)
self.parent = parent
self.value = []
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
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 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 @@ class FieldsMapper(QgisAlgorithm):
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)
self.expressions.append(expression)
return True
writer = output.getVectorWriter(fields, layer.wkbType(), layer.crs(), context)
def outputFields(self, inputFields):
return self.fields
# 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
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)
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())))
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

View File

@ -160,7 +160,7 @@ from .ZonalStatistics import ZonalStatistics
# 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 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
ExtentFromLayer(),
ExtractNodes(),
ExtractSpecificNodes(),
FieldsMapper(),
FixedDistanceBuffer(),
FixGeometry(),
GeometryByExpression(),

View File

@ -31,17 +31,38 @@ import os
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 @@ WIDGET, BASE = uic.loadUiType(
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 @@ class FieldsMappingModel(QAbstractTableModel):
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 @@ class FieldsMappingModel(QAbstractTableModel):
def setLayer(self, layer):
self._layer = layer
self.testAllExpressions()
def columnCount(self, parent=QModelIndex()):
if parent.isValid():
@ -133,70 +148,45 @@ class FieldsMappingModel(QAbstractTableModel):
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 @@ class FieldsMappingModel(QAbstractTableModel):
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 @@ class FieldsMappingModel(QAbstractTableModel):
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 @@ class FieldsMappingModel(QAbstractTableModel):
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
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 setEditorData(self, editor, index):
if not editor:
return
value = index.model().data(index, Qt.EditRole)
editor.setCurrentIndex(editor.findData(value))
elif fieldType == QgsExpression:
editor = QgsFieldExpressionWidget(parent)
editor.setLayer(index.model().layer())
editor.registerExpressionContextGenerator(index.model().contextGenerator())
editor.fieldChanged.connect(self.on_expression_fieldChange)
def setModelData(self, editor, model, index):
if not editor:
return
value = editor.currentData()
if value is None:
value = QVariant.Invalid
model.setData(index, value)
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 @@ class FieldsMappingPanel(BASE, WIDGET):
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 @@ class FieldsMappingPanel(BASE, WIDGET):
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 @@ class FieldsMappingPanel(BASE, WIDGET):
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 @@ class FieldsMappingPanel(BASE, WIDGET):
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 @@ class FieldsMappingPanel(BASE, WIDGET):
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 @@ class FieldsMappingPanel(BASE, WIDGET):
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 @@ class FieldsMappingWidgetWrapper(WidgetWrapper):
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)

View File

@ -0,0 +1,32 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>refactorfields</Name>
<ElementPath>refactorfields</ElementPath>
<!--MULTIPOLYGON-->
<GeometryType>6</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>4</FeatureCount>
<ExtentXMin>0.00000</ExtentXMin>
<ExtentXMax>9.00000</ExtentXMax>
<ExtentYMin>-1.00000</ExtentYMin>
<ExtentYMax>6.00000</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>Bname</Name>
<ElementPath>Bname</ElementPath>
<Type>String</Type>
<Width>19</Width>
</PropertyDefn>
<PropertyDefn>
<Name>Bintval</Name>
<ElementPath>Bintval</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>Bfloatval</Name>
<ElementPath>Bfloatval</ElementPath>
<Type>Real</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0</gml:X><gml:Y>-1</gml:Y></gml:coord>
<gml:coord><gml:X>9</gml:X><gml:Y>6</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:refactorfields fid="refactorfields.0">
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2,1 2,2 3,2 3,3 4,3 4,1 2,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
<ogr:row_number>0</ogr:row_number>
<ogr:Bname>multipolys.0 - Test</ogr:Bname>
<ogr:Bintval>2</ogr:Bintval>
<ogr:Bfloatval>0.246</ogr:Bfloatval>
</ogr:refactorfields>
</gml:featureMember>
<gml:featureMember>
<ogr:refactorfields fid="refactorfields.1">
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>7,-1 8,-1 8,3 7,3 7,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>7,6 7,5 7,4 8,4 9,5 9,6 7,6</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
<ogr:row_number>1</ogr:row_number>
</ogr:refactorfields>
</gml:featureMember>
<gml:featureMember>
<ogr:refactorfields fid="refactorfields.2">
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>0,0 0,1 1,1 1,0 0,0</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
<ogr:row_number>2</ogr:row_number>
<ogr:Bname>multipolys.2 - Test</ogr:Bname>
<ogr:Bintval>3</ogr:Bintval>
<ogr:Bfloatval>-0.246</ogr:Bfloatval>
</ogr:refactorfields>
</gml:featureMember>
<gml:featureMember>
<ogr:refactorfields fid="refactorfields.3">
<ogr:row_number>3</ogr:row_number>
<ogr:Bname>multipolys.3 - Test</ogr:Bname>
<ogr:Bintval>4</ogr:Bintval>
<ogr:Bfloatval>0</ogr:Bfloatval>
</ogr:refactorfields>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -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

View File

@ -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

View File

@ -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