mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-16 00:05:45 -04:00
Use QgsFieldMappingWigdet in processing UI
This commit is contained in:
parent
dcb4987079
commit
e1044d87f1
@ -22,49 +22,36 @@ __date__ = 'October 2014'
|
||||
__copyright__ = '(C) 2014, Arnaud Morvan'
|
||||
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
from qgis.PyQt import uic
|
||||
from qgis.PyQt.QtCore import (
|
||||
QItemSelectionModel,
|
||||
QAbstractTableModel,
|
||||
QModelIndex,
|
||||
QVariant,
|
||||
Qt,
|
||||
pyqtSlot,
|
||||
QCoreApplication
|
||||
)
|
||||
|
||||
from qgis.PyQt.QtGui import (
|
||||
QBrush,
|
||||
QColor
|
||||
QCoreApplication,
|
||||
QVariant,
|
||||
)
|
||||
|
||||
from qgis.PyQt.QtWidgets import (
|
||||
QComboBox,
|
||||
QHeaderView,
|
||||
QLineEdit,
|
||||
QSpacerItem,
|
||||
QMessageBox,
|
||||
QSpinBox,
|
||||
QStyledItemDelegate,
|
||||
QWidget,
|
||||
QVBoxLayout
|
||||
)
|
||||
|
||||
from qgis.core import (
|
||||
QgsApplication,
|
||||
QgsExpression,
|
||||
QgsMapLayerProxyModel,
|
||||
QgsProcessingFeatureSourceDefinition,
|
||||
QgsProcessingUtils,
|
||||
QgsProject,
|
||||
QgsVectorLayer,
|
||||
QgsFieldConstraints
|
||||
QgsField,
|
||||
QgsFields,
|
||||
QgsExpression,
|
||||
)
|
||||
from qgis.gui import QgsFieldExpressionWidget
|
||||
|
||||
from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD, DIALOG_MODELER, DIALOG_BATCH
|
||||
from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD, DIALOG_MODELER
|
||||
from processing.tools import dataobjects
|
||||
from processing.algs.qgis.FieldsMapper import FieldsMapper
|
||||
|
||||
@ -74,280 +61,6 @@ WIDGET, BASE = uic.loadUiType(
|
||||
os.path.join(pluginPath, 'fieldsmappingpanelbase.ui'))
|
||||
|
||||
|
||||
class FieldsMappingModel(QAbstractTableModel):
|
||||
|
||||
fieldTypes = OrderedDict([
|
||||
(QVariant.Date, "Date"),
|
||||
(QVariant.DateTime, "DateTime"),
|
||||
(QVariant.Double, "Double"),
|
||||
(QVariant.Int, "Integer"),
|
||||
(QVariant.LongLong, "Integer64"),
|
||||
(QVariant.String, "String"),
|
||||
(QVariant.List, "List"),
|
||||
(QVariant.Bool, "Boolean")])
|
||||
|
||||
constraints = {
|
||||
QgsFieldConstraints.ConstraintNotNull: "NOT NULL",
|
||||
QgsFieldConstraints.ConstraintUnique: "Unique",
|
||||
QgsFieldConstraints.ConstraintExpression: "Expression constraint"
|
||||
}
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(FieldsMappingModel, self).__init__(parent)
|
||||
self._mapping = []
|
||||
self._layer = None
|
||||
self.configure()
|
||||
self._generator = None
|
||||
|
||||
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")
|
||||
}, {
|
||||
'name': 'constraints',
|
||||
'type': QVariant.String,
|
||||
'header': self.tr("Template properties")
|
||||
}]
|
||||
|
||||
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
|
||||
|
||||
def setMapping(self, value):
|
||||
self.beginResetModel()
|
||||
self._mapping = value
|
||||
self.endResetModel()
|
||||
|
||||
def setContextGenerator(self, generator):
|
||||
self._generator = generator
|
||||
|
||||
def contextGenerator(self):
|
||||
if self._generator:
|
||||
return self._generator
|
||||
if self._layer:
|
||||
return self._layer
|
||||
return QgsProject.instance()
|
||||
|
||||
def layer(self):
|
||||
return self._layer
|
||||
|
||||
def setLayer(self, layer):
|
||||
self._layer = layer
|
||||
|
||||
def columnCount(self, parent=QModelIndex()):
|
||||
if parent.isValid():
|
||||
return 0
|
||||
return len(self.columns)
|
||||
|
||||
def rowCount(self, parent=QModelIndex()):
|
||||
if parent.isValid():
|
||||
return 0
|
||||
try:
|
||||
return len(self._mapping)
|
||||
except TypeError:
|
||||
return 0
|
||||
|
||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||
if role == Qt.DisplayRole:
|
||||
if orientation == Qt.Horizontal:
|
||||
return self.columns[section]['header']
|
||||
if orientation == Qt.Vertical:
|
||||
return section
|
||||
|
||||
def flags(self, index):
|
||||
column_def = self.columns[index.column()]
|
||||
|
||||
flags = Qt.ItemFlags(Qt.ItemIsSelectable |
|
||||
Qt.ItemIsEnabled)
|
||||
if column_def['name'] != 'constraints':
|
||||
flags = flags | Qt.ItemIsEditable
|
||||
|
||||
return flags
|
||||
|
||||
def data(self, index, role=Qt.DisplayRole):
|
||||
field = self._mapping[index.row()]
|
||||
column_def = self.columns[index.column()]
|
||||
|
||||
if role == Qt.DisplayRole:
|
||||
value = field[column_def['name']] if column_def['name'] in field else QVariant()
|
||||
if column_def['type'] == QVariant.Type:
|
||||
if value == QVariant.Invalid:
|
||||
return ''
|
||||
return self.fieldTypes[value]
|
||||
elif column_def['name'] == 'constraints' and value:
|
||||
return self.tr("Constraints active")
|
||||
|
||||
return value
|
||||
|
||||
if role == Qt.EditRole:
|
||||
return field[column_def['name']]
|
||||
|
||||
if role == Qt.TextAlignmentRole:
|
||||
if column_def['type'] in [QVariant.Int]:
|
||||
hAlign = Qt.AlignRight
|
||||
else:
|
||||
hAlign = Qt.AlignLeft
|
||||
return hAlign + Qt.AlignVCenter
|
||||
|
||||
if role == Qt.BackgroundRole:
|
||||
return QBrush(QColor(255, 224, 178)) if 'constraints' in field and field['constraints'] else QVariant()
|
||||
|
||||
if role == Qt.ToolTipRole:
|
||||
if column_def['name'] == 'constraints' and 'constraints' in field:
|
||||
return "<br>".join([self.constraints[constraint] for constraint in field['constraints']])
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
field = self._mapping[index.row()]
|
||||
column_def = self.columns[index.column()]
|
||||
|
||||
if role == Qt.EditRole:
|
||||
field[column_def['name']] = value
|
||||
self.dataChanged.emit(index, index)
|
||||
|
||||
return True
|
||||
|
||||
def insertRows(self, row, count, index=QModelIndex()):
|
||||
self.beginInsertRows(index, row, row + count - 1)
|
||||
|
||||
for i in range(count):
|
||||
field = self.newField()
|
||||
self._mapping.insert(row + i, field)
|
||||
|
||||
self.endInsertRows()
|
||||
return True
|
||||
|
||||
def removeRows(self, row, count, index=QModelIndex()):
|
||||
self.beginRemoveRows(index, row, row + count - 1)
|
||||
|
||||
for i in range(row + count - 1, row + 1):
|
||||
self._mapping.pop(i)
|
||||
|
||||
self.endRemoveRows()
|
||||
return True
|
||||
|
||||
def newField(self, field=None):
|
||||
if field is None:
|
||||
return {'name': '',
|
||||
'type': QVariant.Invalid,
|
||||
'length': 0,
|
||||
'precision': 0,
|
||||
'expression': '',
|
||||
'constraints': ''}
|
||||
|
||||
return {'name': field.name(),
|
||||
'type': field.type(),
|
||||
'length': field.length(),
|
||||
'precision': field.precision(),
|
||||
'expression': QgsExpression.quotedColumnRef(field.name()),
|
||||
'constraints': self.get_field_constraints(field.constraints())}
|
||||
|
||||
def loadLayerFields(self, layer):
|
||||
self.beginResetModel()
|
||||
|
||||
self._mapping = []
|
||||
if layer is not None:
|
||||
for field in layer.fields():
|
||||
self._mapping.append(self.newField(field))
|
||||
|
||||
self.endResetModel()
|
||||
|
||||
def get_field_constraints(self, field_constraints):
|
||||
constraints = list()
|
||||
|
||||
if field_constraints.constraints() & QgsFieldConstraints.ConstraintNotNull and \
|
||||
field_constraints.constraintStrength(
|
||||
QgsFieldConstraints.ConstraintNotNull) & QgsFieldConstraints.ConstraintStrengthHard:
|
||||
constraints.append(QgsFieldConstraints.ConstraintNotNull)
|
||||
|
||||
if field_constraints.constraints() & QgsFieldConstraints.ConstraintUnique and \
|
||||
field_constraints.constraintStrength(
|
||||
QgsFieldConstraints.ConstraintUnique) & QgsFieldConstraints.ConstraintStrengthHard:
|
||||
constraints.append(QgsFieldConstraints.ConstraintUnique)
|
||||
|
||||
if field_constraints.constraints() & QgsFieldConstraints.ConstraintExpression and \
|
||||
field_constraints.constraintStrength(
|
||||
QgsFieldConstraints.ConstraintExpression) & QgsFieldConstraints.ConstraintStrengthHard:
|
||||
constraints.append(QgsFieldConstraints.ConstraintExpression)
|
||||
|
||||
return constraints
|
||||
|
||||
|
||||
class FieldTypeDelegate(QStyledItemDelegate):
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
editor = QComboBox(parent)
|
||||
for key, text in 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))
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
if not editor:
|
||||
return
|
||||
value = editor.currentData()
|
||||
if value is None:
|
||||
value = QVariant.Invalid
|
||||
model.setData(index, value)
|
||||
|
||||
|
||||
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)
|
||||
editor.setAllowEvalErrors(self.parent().dialogType == DIALOG_MODELER)
|
||||
return editor
|
||||
|
||||
def setEditorData(self, editor, index):
|
||||
if not editor:
|
||||
return
|
||||
value = index.model().data(index, Qt.EditRole)
|
||||
editor.setField(value)
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
if not editor:
|
||||
return
|
||||
(value, isExpression, isValid) = editor.currentField()
|
||||
if isExpression is True:
|
||||
model.setData(index, value)
|
||||
else:
|
||||
model.setData(index, QgsExpression.quotedColumnRef(value))
|
||||
|
||||
def on_expression_fieldChange(self, fieldName):
|
||||
self.commitData.emit(self.sender())
|
||||
|
||||
|
||||
class FieldsMappingPanel(BASE, WIDGET):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
@ -362,35 +75,20 @@ class FieldsMappingPanel(BASE, WIDGET):
|
||||
|
||||
self.configure()
|
||||
|
||||
self.model.modelReset.connect(self.on_model_modelReset)
|
||||
self.model.rowsInserted.connect(self.on_model_rowsInserted)
|
||||
|
||||
self.layerCombo.setAllowEmptyLayer(True)
|
||||
self.layerCombo.setFilters(QgsMapLayerProxyModel.VectorLayer)
|
||||
self.dialogType = None
|
||||
self.layer = None
|
||||
|
||||
def configure(self):
|
||||
self.model = FieldsMappingModel()
|
||||
self.fieldsView.setModel(self.model)
|
||||
|
||||
self.setDelegate('expression', ExpressionDelegate(self))
|
||||
self.setDelegate('type', FieldTypeDelegate(self))
|
||||
|
||||
def setContextGenerator(self, generator):
|
||||
self.model.setContextGenerator(generator)
|
||||
|
||||
def setDelegate(self, column_name, delegate):
|
||||
self.fieldsView.setItemDelegateForColumn(
|
||||
self.model.columnIndex(column_name),
|
||||
delegate)
|
||||
self.model = self.fieldsView.model()
|
||||
self.fieldsView.setDestinationEditable(True)
|
||||
|
||||
def setLayer(self, layer):
|
||||
if self.model.layer() == layer:
|
||||
if layer is None or self.layer == layer:
|
||||
return
|
||||
self.model.setLayer(layer)
|
||||
if layer is None:
|
||||
return
|
||||
if self.model.rowCount() == 0:
|
||||
self.layer = layer
|
||||
if self.model.rowCount(QModelIndex()) == 0:
|
||||
self.on_resetButton_clicked()
|
||||
return
|
||||
dlg = QMessageBox(self)
|
||||
@ -403,15 +101,43 @@ class FieldsMappingPanel(BASE, WIDGET):
|
||||
self.on_resetButton_clicked()
|
||||
|
||||
def value(self):
|
||||
return self.model.mapping()
|
||||
# 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.expression(),
|
||||
})
|
||||
return results
|
||||
|
||||
def setValue(self, value):
|
||||
self.model.setMapping(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()] = QgsExpression(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()
|
||||
self.model.insertRows(rowCount, 1)
|
||||
rowCount = self.model.rowCount(QModelIndex())
|
||||
self.model.appendField(QgsField('new_field'))
|
||||
index = self.model.index(rowCount, 0)
|
||||
self.fieldsView.selectionModel().select(
|
||||
index,
|
||||
@ -421,118 +147,32 @@ class FieldsMappingPanel(BASE, WIDGET):
|
||||
QItemSelectionModel.Current |
|
||||
QItemSelectionModel.Rows))
|
||||
self.fieldsView.scrollTo(index)
|
||||
self.fieldsView.scrollTo(index)
|
||||
|
||||
@pyqtSlot(bool, name='on_deleteButton_clicked')
|
||||
def on_deleteButton_clicked(self, checked=False):
|
||||
sel = self.fieldsView.selectionModel()
|
||||
if not sel.hasSelection():
|
||||
return
|
||||
|
||||
indexes = sel.selectedRows()
|
||||
for index in indexes:
|
||||
self.model.removeRows(index.row(), 1)
|
||||
self.fieldsView.removeSelectedFields()
|
||||
|
||||
@pyqtSlot(bool, name='on_upButton_clicked')
|
||||
def on_upButton_clicked(self, checked=False):
|
||||
sel = self.fieldsView.selectionModel()
|
||||
if not sel.hasSelection():
|
||||
return
|
||||
|
||||
row = sel.selectedRows()[0].row()
|
||||
if row == 0:
|
||||
return
|
||||
|
||||
self.model.insertRows(row - 1, 1)
|
||||
|
||||
for column in range(self.model.columnCount()):
|
||||
srcIndex = self.model.index(row + 1, column)
|
||||
dstIndex = self.model.index(row - 1, column)
|
||||
value = self.model.data(srcIndex, Qt.EditRole)
|
||||
self.model.setData(dstIndex, value, Qt.EditRole)
|
||||
|
||||
self.model.removeRows(row + 1, 1)
|
||||
|
||||
sel.select(
|
||||
self.model.index(row - 1, 0),
|
||||
QItemSelectionModel.SelectionFlags(
|
||||
QItemSelectionModel.Clear |
|
||||
QItemSelectionModel.Select |
|
||||
QItemSelectionModel.Current |
|
||||
QItemSelectionModel.Rows))
|
||||
self.fieldsView.moveSelectedFieldsUp()
|
||||
|
||||
@pyqtSlot(bool, name='on_downButton_clicked')
|
||||
def on_downButton_clicked(self, checked=False):
|
||||
sel = self.fieldsView.selectionModel()
|
||||
if not sel.hasSelection():
|
||||
return
|
||||
|
||||
row = sel.selectedRows()[0].row()
|
||||
if row == self.model.rowCount() - 1:
|
||||
return
|
||||
|
||||
self.model.insertRows(row + 2, 1)
|
||||
|
||||
for column in range(self.model.columnCount()):
|
||||
srcIndex = self.model.index(row, column)
|
||||
dstIndex = self.model.index(row + 2, column)
|
||||
value = self.model.data(srcIndex, Qt.EditRole)
|
||||
self.model.setData(dstIndex, value, Qt.EditRole)
|
||||
|
||||
self.model.removeRows(row, 1)
|
||||
|
||||
sel.select(
|
||||
self.model.index(row + 1, 0),
|
||||
QItemSelectionModel.SelectionFlags(
|
||||
QItemSelectionModel.Clear |
|
||||
QItemSelectionModel.Select |
|
||||
QItemSelectionModel.Current |
|
||||
QItemSelectionModel.Rows))
|
||||
self.fieldsView.moveSelectedFieldsDown()
|
||||
|
||||
@pyqtSlot(bool, name='on_resetButton_clicked')
|
||||
def on_resetButton_clicked(self, checked=False):
|
||||
self.model.loadLayerFields(self.model.layer())
|
||||
|
||||
def resizeColumns(self):
|
||||
header = self.fieldsView.horizontalHeader()
|
||||
header.resizeSections(QHeaderView.ResizeToContents)
|
||||
for section in range(header.count()):
|
||||
size = header.sectionSize(section)
|
||||
fieldType = self.model.columns[section]['type']
|
||||
if fieldType == QgsExpression:
|
||||
header.resizeSection(section, size + 100)
|
||||
else:
|
||||
header.resizeSection(section, size + 20)
|
||||
|
||||
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):
|
||||
lineEdit = editor.findChild(QLineEdit)
|
||||
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):
|
||||
for row in range(start, end + 1):
|
||||
self.openPersistentEditors(row)
|
||||
"""Load fields from layer"""
|
||||
if self.layer:
|
||||
self.fieldsView.setDestinationFields(self.layer.fields())
|
||||
self.on_loadLayerFieldsButton_clicked()
|
||||
|
||||
@pyqtSlot(bool, name='on_loadLayerFieldsButton_clicked')
|
||||
def on_loadLayerFieldsButton_clicked(self, checked=False):
|
||||
layer = self.layerCombo.currentLayer()
|
||||
if layer is None:
|
||||
return
|
||||
self.model.loadLayerFields(layer)
|
||||
self.fieldsView.setSourceFields(layer.fields())
|
||||
|
||||
|
||||
class FieldsMappingWidgetWrapper(WidgetWrapper):
|
||||
@ -548,8 +188,6 @@ class FieldsMappingWidgetWrapper(WidgetWrapper):
|
||||
self.panel = self.createPanel()
|
||||
self.panel.dialogType = self.dialogType
|
||||
|
||||
self.panel.setContextGenerator(self)
|
||||
|
||||
if self.dialogType == DIALOG_MODELER:
|
||||
self.combobox = QComboBox()
|
||||
self.combobox.addItem(QCoreApplication.translate('Processing', '[Preconfigure]'), None)
|
||||
|
@ -20,18 +20,27 @@
|
||||
<string>Fields</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QTableView" name="fieldsView">
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
<widget class="QgsFieldMappingWidget" name="fieldsView" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -150,6 +159,12 @@
|
||||
<header>qgis.gui</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QgsFieldMappingWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>qgis.gui</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user