From 9e51488a888ef420767e2c9cbe83be047063e278 Mon Sep 17 00:00:00 2001 From: "arnaud.morvan@camptocamp.com" Date: Thu, 22 Jan 2015 14:52:52 +0100 Subject: [PATCH] Create FieldMapper GeoAlgorithm --- images/images.qrc | 1 + .../processing/algs/qgis/FieldsMapper.py | 137 +++++ .../algs/qgis/QGISAlgorithmProvider.py | 2 + .../processing/algs/qgis/fieldsmapping.py | 55 +++ .../algs/qgis/ui/FieldsMapperDialogs.py | 140 ++++++ .../algs/qgis/ui/FieldsMappingPanel.py | 466 ++++++++++++++++++ .../algs/qgis/ui/ui_widgetFieldsMapping.py | 107 ++++ .../algs/qgis/ui/widgetFieldsMapping.ui | 148 ++++++ 8 files changed, 1056 insertions(+) create mode 100644 python/plugins/processing/algs/qgis/FieldsMapper.py create mode 100644 python/plugins/processing/algs/qgis/fieldsmapping.py create mode 100644 python/plugins/processing/algs/qgis/ui/FieldsMapperDialogs.py create mode 100644 python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py create mode 100644 python/plugins/processing/algs/qgis/ui/ui_widgetFieldsMapping.py create mode 100644 python/plugins/processing/algs/qgis/ui/widgetFieldsMapping.ui diff --git a/images/images.qrc b/images/images.qrc index d0d201d9d04..fee2bf887b7 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -478,6 +478,7 @@ themes/default/cadtools/perpendicular.png themes/default/mIconSuccess.png themes/default/bubble.svg + themes/default/mIconClear.png qgis_tips/symbol_levels.png diff --git a/python/plugins/processing/algs/qgis/FieldsMapper.py b/python/plugins/processing/algs/qgis/FieldsMapper.py new file mode 100644 index 00000000000..7bf3cff6ec8 --- /dev/null +++ b/python/plugins/processing/algs/qgis/FieldsMapper.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + FieldsMapper.py + --------------------- + Date : October 2014 + Copyright : (C) 2014 by Arnaud Morvan + Email : arnaud dot morvan at camptocamp 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. * +* * +*************************************************************************** +""" + +__author__ = 'Arnaud Morvan' +__date__ = 'October 2014' +__copyright__ = '(C) 2014, Arnaud Morvan' + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = '$Format:%H$' + + +from qgis.core import QgsField, QgsExpression, QgsFeature +from processing.core.GeoAlgorithm import GeoAlgorithm +from processing.core.GeoAlgorithmExecutionException import \ + GeoAlgorithmExecutionException +from processing.core.parameters import ParameterVector +from processing.core.outputs import OutputVector +from processing.tools import dataobjects, vector + +from .fieldsmapping import ParameterFieldsMapping +from .ui.FieldsMapperDialogs import (FieldsMapperParametersDialog, + FieldsMapperModelerParametersDialog) + +class FieldsMapper(GeoAlgorithm): + + INPUT_LAYER = 'INPUT_LAYER' + FIELDS_MAPPING = 'FIELDS_MAPPING' + OUTPUT_LAYER = 'OUTPUT_LAYER' + + def __init__(self): + GeoAlgorithm.__init__(self) + self.mapping = None + + def defineCharacteristics(self): + self.name = 'Refactor fields' + self.group = 'Vector table tools' + self.addParameter(ParameterVector(self.INPUT_LAYER, + self.tr('Input layer'), + [ParameterVector.VECTOR_TYPE_ANY], False)) + self.addParameter(ParameterFieldsMapping(self.FIELDS_MAPPING, + self.tr('Fields mapping'), self.INPUT_LAYER)) + self.addOutput(OutputVector(self.OUTPUT_LAYER, + self.tr('Output layer'))) + + def getCustomParametersDialog(self): + return FieldsMapperParametersDialog(self) + + def getCustomModelerParametersDialog(self, modelAlg, algIndex=None): + return FieldsMapperModelerParametersDialog(self, modelAlg, algIndex) + + def processAlgorithm(self, progress): + layer = self.getParameterValue(self.INPUT_LAYER) + mapping = self.getParameterValue(self.FIELDS_MAPPING) + output = self.getOutputFromName(self.OUTPUT_LAYER) + + layer = dataobjects.getObjectFromUri(layer) + provider = layer.dataProvider() + fields = [] + expressions = [] + for field_def in mapping: + fields.append(QgsField(name=field_def['name'], + type=field_def['type'], + len=field_def['length'], + prec=field_def['precision'])) + + expression = QgsExpression(field_def['expression']) + if expression.hasParserError(): + raise GeoAlgorithmExecutionException( + self.tr(u'Parser error in expression "{}": {}') + .format(unicode(field_def['expression']), + unicode(expression.parserErrorString()))) + expression.prepare(provider.fields()) + if expression.hasEvalError(): + raise GeoAlgorithmExecutionException( + self.tr(u'Evaluation error in expression "{}": {}') + .format(unicode(field_def['expression']), + unicode(expression.evalErrorString()))) + expressions.append(expression) + + writer = output.getVectorWriter(fields, + provider.geometryType(), + layer.crs()) + + # Create output vector layer with new attributes + error = '' + calculationSuccess = True + inFeat = QgsFeature() + outFeat = QgsFeature() + features = vector.features(layer) + count = len(features) + for current, inFeat in enumerate(features): + rownum = current + 1 + + outFeat.setGeometry(inFeat.geometry()) + + attrs = [] + for i in xrange(0, len(mapping)): + field_def = mapping[i] + expression = expressions[i] + expression.setCurrentRowNumber(rownum) + value = expression.evaluate(inFeat) + if expression.hasEvalError(): + calculationSuccess = False + error = expression.evalErrorString() + break + + attrs.append(value) + outFeat.setAttributes(attrs) + + writer.addFeature(outFeat) + + current += 1 + progress.setPercentage(100 * current / float(count)) + + del writer + + if not calculationSuccess: + raise GeoAlgorithmExecutionException( + self.tr('An error occurred while evaluating the calculation' + ' string:\n') + error) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 8de76941ee2..228041a63ef 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -125,6 +125,7 @@ from SetRasterStyle import SetRasterStyle from SelectByExpression import SelectByExpression from HypsometricCurves import HypsometricCurves from SplitLinesWithLines import SplitLinesWithLines +from processing.algs.qgis.FieldsMapper import FieldsMapper import processing.resources_rc @@ -171,6 +172,7 @@ class QGISAlgorithmProvider(AlgorithmProvider): SetVectorStyle(), SetRasterStyle(), SelectByExpression(), HypsometricCurves(), SplitLinesWithLines(), CreateConstantRaster(), + FieldsMapper(), ] if hasMatplotlib: diff --git a/python/plugins/processing/algs/qgis/fieldsmapping.py b/python/plugins/processing/algs/qgis/fieldsmapping.py new file mode 100644 index 00000000000..2921f2a3dd2 --- /dev/null +++ b/python/plugins/processing/algs/qgis/fieldsmapping.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + FieldsMapper.py + --------------------- + Date : October 2014 + Copyright : (C) 2014 by Arnaud Morvan + Email : arnaud dot morvan at camptocamp 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. * +* * +*************************************************************************** +""" + +__author__ = 'Arnaud Morvan' +__date__ = 'October 2014' +__copyright__ = '(C) 2014, Arnaud Morvan' + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = '$Format:%H$' + + +from processing.core.parameters import Parameter + + +class ParameterFieldsMapping(Parameter): + + def __init__(self, name='', description='', parent=None): + Parameter.__init__(self, name, description) + self.parent = parent + self.value = [] + + def getValueAsCommandLineParameter(self): + return '"' + unicode(self.value) + '"' + + def setValue(self, value): + if value is None: + return False + if isinstance(value, list): + self.value = value + return True + if isinstance(value, unicode): + try: + self.value = eval(value) + return True + except Exception as e: + print unicode(e) # display error in console + return False + return False diff --git a/python/plugins/processing/algs/qgis/ui/FieldsMapperDialogs.py b/python/plugins/processing/algs/qgis/ui/FieldsMapperDialogs.py new file mode 100644 index 00000000000..27b3ffa6040 --- /dev/null +++ b/python/plugins/processing/algs/qgis/ui/FieldsMapperDialogs.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + FieldsMapper.py + --------------------- + Date : October 2014 + Copyright : (C) 2014 by Arnaud Morvan + Email : arnaud dot morvan at camptocamp 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. * +* * +*************************************************************************** +""" + +__author__ = 'Arnaud Morvan' +__date__ = 'October 2014' +__copyright__ = '(C) 2014, Arnaud Morvan' + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = '$Format:%H$' + + +from PyQt4.QtGui import QComboBox, QSpacerItem + +from processing.core.parameters import ParameterVector +from processing.tools import dataobjects +from processing.gui.ParametersPanel import ParametersPanel +from processing.gui.AlgorithmDialog import AlgorithmDialog, AlgorithmDialogBase +from processing.modeler.ModelerParametersDialog import ModelerParametersDialog + +from processing.algs.qgis.fieldsmapping import ParameterFieldsMapping +from .FieldsMappingPanel import FieldsMappingPanel + + +class FieldsMapperParametersPanel(ParametersPanel): + + def __init__(self, parent, alg): + ParametersPanel.__init__(self, parent, alg) + + item = self.layoutMain.itemAt(self.layoutMain.count() - 1) + if isinstance(item, QSpacerItem): + self.layoutMain.removeItem(item) + item = None + + def getWidgetFromParameter(self, param): + if isinstance(param, ParameterFieldsMapping): + item = FieldsMappingPanel() + if param.parent in self.dependentItems: + items = self.dependentItems[param.parent] + else: + items = [] + self.dependentItems[param.parent] = items + items.append(param.name) + parent = self.alg.getParameterFromName(param.parent) + if isinstance(parent, ParameterVector): + layers = dataobjects.getVectorLayers(parent.shapetype) + else: + layers = dataobjects.getTables() + if len(layers) > 0: + item.setLayer(layers[0]) + return item + return ParametersPanel.getWidgetFromParameter(self, param) + + def updateDependentFields(self): + sender = self.sender() + if not isinstance(sender, QComboBox): + return + if not sender.name in self.dependentItems: + return + layer = sender.itemData(sender.currentIndex()) + children = self.dependentItems[sender.name] + for child in children: + widget = self.valueItems[child] + if isinstance(widget, FieldsMappingPanel): + widget.setLayer(layer) + + def somethingDependsOnThisParameter(self, parent): + for param in self.alg.parameters: + if isinstance(param, ParameterFieldsMapping): + if param.parent == parent.name: + return True + return False + + +class FieldsMapperParametersDialog(AlgorithmDialog): + def __init__(self, alg): + AlgorithmDialogBase.__init__(self, alg) + + self.alg = alg + + self.mainWidget = FieldsMapperParametersPanel(self, alg) + self.setMainWidget() + + def setParamValue(self, param, widget, alg=None): + if isinstance(param, ParameterFieldsMapping): + return param.setValue(widget.value()) + return AlgorithmDialog.setParamValue(self, param, widget, alg) + + +class FieldsMapperModelerParametersDialog(ModelerParametersDialog): + + def __init__(self, alg, model, algName=None): + ModelerParametersDialog.__init__(self, alg, model, algName) + + paramsLayout = self.paramPanel.layout() + item = paramsLayout.itemAt(paramsLayout.count() - 1) + if isinstance(item, QSpacerItem): + paramsLayout.removeItem(item) + item = None + + def getWidgetFromParameter(self, param): + if isinstance(param, ParameterFieldsMapping): + return FieldsMappingPanel() + return ModelerParametersDialog.getWidgetFromParameter(self, param) + + def setPreviousValues(self): + ModelerParametersDialog.setPreviousValues(self) + if self._algName is not None: + alg = self.model.algs[self._algName] + for param in alg.algorithm.parameters: + if isinstance(param, ParameterFieldsMapping): + widget = self.valueItems[param.name] + value = alg.params[param.name] + if isinstance(value, unicode): + # convert to list because of ModelerAlgorithme.resolveValue behavior with lists + value = eval(value) + widget.setValue(value) + + def setParamValue(self, alg, param, widget): + if isinstance(param, ParameterFieldsMapping): + # convert to unicode because of ModelerAlgorithme.resolveValue behavior with lists + alg.params[param.name] = unicode(widget.value()) + return True + return ModelerParametersDialog.setParamValue(self, alg, param, widget) diff --git a/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py b/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py new file mode 100644 index 00000000000..9c055bd6ae6 --- /dev/null +++ b/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py @@ -0,0 +1,466 @@ +# -*- coding: utf-8 -*- + +""" +*************************************************************************** + FieldsMappingWidget.py + --------------------- + Date : October 2014 + Copyright : (C) 2014 by Arnaud Morvan + Email : arnaud dot morvan at camptocamp 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. * +* * +*************************************************************************** +""" + +__author__ = 'Arnaud Morvan' +__date__ = 'October 2014' +__copyright__ = '(C) 2014, Arnaud Morvan' + +# This will get replaced with a git SHA1 when you do a git archive + +__revision__ = '$Format:%H$' + + +from collections import OrderedDict + +from PyQt4 import QtCore, QtGui + +from qgis.core import QgsExpression +from qgis.gui import QgsFieldExpressionWidget + +from processing.tools import dataobjects + +from .ui_widgetFieldsMapping import Ui_Form + + +class FieldsMappingModel(QtCore.QAbstractTableModel): + + fieldTypes = OrderedDict([ + (QtCore.QVariant.Int, "Integer"), + (QtCore.QVariant.Double, "Double"), + (QtCore.QVariant.String, "String"), + (QtCore.QVariant.Date, "Date")]) + + columns = [ + {'name': 'name', 'type': QtCore.QVariant.String}, + {'name': 'type', 'type': QtCore.QVariant.Type}, + {'name': 'length', 'type': QtCore.QVariant.Int}, + {'name': 'precision', 'type': QtCore.QVariant.Int}, + # {'name': 'comment', 'type': QtCore.QVariant.String}, + {'name': 'expression', 'type': QgsExpression}] + + def __init__(self, parent=None): + super(FieldsMappingModel, self).__init__(parent) + self._mapping = [] + self._errors = [] + self._layer = None + + def mapping(self): + return self._mapping + + def setMapping(self, value): + self.beginResetModel() + self._mapping = value + self.testAllExpressions() + self.endResetModel() + + def testAllExpressions(self): + self._errors = [None for i in xrange(0, len(self._mapping))] + for row in xrange(0, len(self._mapping)): + self.testExpression(row) + + def testExpression(self, row): + self._errors[row] = None + field = self._mapping[row] + expression = QgsExpression(field['expression']) + if expression.hasParserError(): + self._errors[row] = expression.parserErrorString() + return + if self._layer is None: + return + dp = self._layer.dataProvider() + for feature in dp.getFeatures(): + expression.evaluate(feature, dp.fields()) + if expression.hasEvalError(): + self._errors[row] = expression.evalErrorString() + return + break + + def layer(self): + return self._layer + + def setLayer(self, layer): + self._layer = layer + self.testAllExpressions() + + def columnCount(self, parent=QtCore.QModelIndex()): + if parent.isValid(): + return 0 + return len(self.columns) + + def rowCount(self, parent=QtCore.QModelIndex()): + if parent.isValid(): + return 0 + return self._mapping.__len__() + + def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): + if role == QtCore.Qt.DisplayRole: + if orientation == QtCore.Qt.Horizontal: + return self.columns[section]['name'].title() + if orientation == QtCore.Qt.Vertical: + return section + + def flags(self, index): + flags = (QtCore.Qt.ItemIsSelectable + + QtCore.Qt.ItemIsEditable + + QtCore.Qt.ItemIsEnabled) + + return QtCore.Qt.ItemFlags(flags) + + def data(self, index, role=QtCore.Qt.DisplayRole): + column = index.column() + + if role == QtCore.Qt.DisplayRole: + field = self._mapping[index.row()] + column_def = self.columns[column] + value = field[column_def['name']] + + fieldType = column_def['type'] + if fieldType == QtCore.QVariant.Type: + if value == 0: + return '' + return self.fieldTypes[value] + return value + + if role == QtCore.Qt.EditRole: + field = self._mapping[index.row()] + column_def = self.columns[column] + value = field[column_def['name']] + return value + + if role == QtCore.Qt.TextAlignmentRole: + fieldType = self.columns[column]['type'] + if fieldType in [QtCore.QVariant.Int]: + hAlign = QtCore.Qt.AlignRight + else: + hAlign = QtCore.Qt.AlignLeft + return hAlign + QtCore.Qt.AlignVCenter + + if role == QtCore.Qt.ForegroundRole: + column_def = self.columns[column] + if column_def['name'] == 'expression': + brush = QtGui.QBrush() + if self._errors[index.row()]: + brush.setColor(QtCore.Qt.red) + else: + brush.setColor(QtCore.Qt.black) + return brush + + if role == QtCore.Qt.ToolTipRole: + column_def = self.columns[column] + if column_def['name'] == 'expression': + return self._errors[index.row()] + + def setData(self, index, value, role=QtCore.Qt.EditRole): + if role == QtCore.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=QtCore.QModelIndex()): + self.beginInsertRows(index, row, row + count - 1) + + for i in xrange(0, count): + field = self.newField() + self._mapping.insert(row + i, field) + self._errors.insert(row + i, None) + self.testExpression(row) + + self.endInsertRows() + return True + + def removeRows(self, row, count, index=QtCore.QModelIndex()): + self.beginRemoveRows(index, row, row + count - 1) + + for i in xrange(row + count - 1, row + 1): + self._mapping.pop(i) + self._errors.pop(i) + + self.endRemoveRows() + return True + + def newField(self, field=None): + if field is None: + return {'name': '', + 'type': QtCore.QVariant.Invalid, + 'length': 0, + 'precision': 0, + 'expression': ''} + + return {'name': field.name(), + 'type': field.type(), + 'length': field.length(), + 'precision': field.precision(), + 'expression': field.name()} + + def loadLayerFields(self, layer): + self.beginResetModel() + + self._mapping = [] + if layer is not None: + dp = layer.dataProvider() + for field in dp.fields(): + self._mapping.append(self.newField(field)) + self.testAllExpressions() + + self.endResetModel() + + +class FieldDelegate(QtGui.QStyledItemDelegate): + + def __init__(self, parent=None): + super(FieldDelegate, self).__init__(parent) + + def createEditor(self, parent, option, index): + column = index.column() + + fieldType = FieldsMappingModel.columns[column]['type'] + if fieldType == QtCore.QVariant.Type: + editor = QtGui.QComboBox(parent) + for key, text in FieldsMappingModel.fieldTypes.iteritems(): + editor.addItem(text, key) + + elif fieldType == QgsExpression: + editor = QgsFieldExpressionWidget(parent) + editor.setLayer(index.model().layer()) + # editor.fieldChanged.connect(self.on_expression_fieldChange) + + else: + editor = QtGui.QStyledItemDelegate.createEditor(self, parent, option, index) + + editor.setAutoFillBackground(True) + return editor + + def setEditorData(self, editor, index): + if not editor: + return + + column = index.column() + value = index.model().data(index, QtCore.Qt.EditRole) + + fieldType = FieldsMappingModel.columns[column]['type'] + if fieldType == QtCore.QVariant.Type: + editor.setCurrentIndex(editor.findData(value)) + + elif fieldType == QgsExpression: + editor.setField(value) + + else: + QtGui.QStyledItemDelegate.setEditorData(self, editor, index) + + def setModelData(self, editor, model, index): + if not editor: + return + + column = index.column() + + fieldType = FieldsMappingModel.columns[column]['type'] + if fieldType == QtCore.QVariant.Type: + value = editor.itemData(editor.currentIndex()) + model.setData(index, value) + + elif fieldType == QgsExpression: + (value, isExpression, isValid) = editor.currentField() + model.setData(index, value) + + else: + QtGui.QStyledItemDelegate.setModelData(self, editor, model, index) + + def updateEditorGeometry(self, editor, option, index): + editor.setGeometry(option.rect) + + def on_expression_fieldChange(self, fieldName, isValid): + # self.commitData.emit(self.sender()) + pass + + +class FieldsMappingPanel(QtGui.QWidget, Ui_Form): + + def __init__(self, parent=None): + QtGui.QWidget.__init__(self, parent) + self.setupUi(self) + + self.addButton.setIcon( + QtGui.QIcon(':/images/themes/default/mActionAdd.svg')) + self.deleteButton.setIcon( + QtGui.QIcon(':/images/themes/default/mActionRemove.svg')) + self.upButton.setIcon( + QtGui.QIcon(':/images/themes/default/mActionArrowUp.png')) + self.downButton.setIcon( + QtGui.QIcon(':/images/themes/default/mActionArrowDown.png')) + self.resetButton.setIcon( + QtGui.QIcon(':/images/themes/default/mIconClear.png')) + + self.model = FieldsMappingModel() + self.fieldsView.setModel(self.model) + + self.model.rowsInserted.connect(self.on_model_rowsInserted) + self.fieldsView.setItemDelegate(FieldDelegate()) + + self.updateLayerCombo() + + def setLayer(self, layer): + self.model.setLayer(layer) + if self.model.rowCount() == 0: + self.on_resetButton_clicked() + else: + dlg = QtGui.QMessageBox(self) + dlg.setText("Do you want to reset the field mapping?") + dlg.setStandardButtons( + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes + + QtGui.QMessageBox.No)) + dlg.setDefaultButton(QtGui.QMessageBox.No) + if dlg.exec_() == QtGui.QMessageBox.Yes: + self.on_resetButton_clicked() + + def value(self): + return self.model.mapping() + + def setValue(self, value): + self.model.setMapping(value) + + @QtCore.pyqtSlot(bool, name='on_addButton_clicked') + 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, + QtGui.QItemSelectionModel.SelectionFlags(QtGui.QItemSelectionModel.Clear + + QtGui.QItemSelectionModel.Select + + QtGui.QItemSelectionModel.Current + + QtGui.QItemSelectionModel.Rows)) + self.fieldsView.scrollTo(index) + self.fieldsView.scrollTo(index) + + @QtCore.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) + + @QtCore.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 xrange(0, self.model.columnCount()): + srcIndex = self.model.index(row + 1, column) + dstIndex = self.model.index(row - 1, column) + value = self.model.data(srcIndex, QtCore.Qt.EditRole) + self.model.setData(dstIndex, value, QtCore.Qt.EditRole) + + self.model.removeRows(row + 1, 1) + + sel.select(self.model.index(row - 1, 0), + QtGui.QItemSelectionModel.SelectionFlags(QtGui.QItemSelectionModel.Clear + + QtGui.QItemSelectionModel.Select + + QtGui.QItemSelectionModel.Current + + QtGui.QItemSelectionModel.Rows)) + + @QtCore.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 xrange(0, self.model.columnCount()): + srcIndex = self.model.index(row, column) + dstIndex = self.model.index(row + 2, column) + value = self.model.data(srcIndex, QtCore.Qt.EditRole) + self.model.setData(dstIndex, value, QtCore.Qt.EditRole) + + self.model.removeRows(row, 1) + + sel.select(self.model.index(row + 1, 0), + QtGui.QItemSelectionModel.SelectionFlags(QtGui.QItemSelectionModel.Clear + + QtGui.QItemSelectionModel.Select + + QtGui.QItemSelectionModel.Current + + QtGui.QItemSelectionModel.Rows)) + + @QtCore.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(QtGui.QHeaderView.ResizeToContents) + for section in xrange(0, header.count()): + size = header.sectionSize(section) + fieldType = FieldsMappingModel.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 xrange(topLeft.row(), bottomRight.row() + 1): + for column in xrange(topLeft.column(), bottomRight.column() + 1): + self.fieldsView.openPersistentEditor(self.model.index(row, column)) + editor = self.fieldsView.indexWidget(self.model.index(row, column)) + if isinstance(editor, QtGui.QLineEdit): + editor.deselect() + if isinstance(editor, QtGui.QSpinBox): + lineEdit = editor.findChild(QtGui.QLineEdit) + lineEdit.setAlignment(QtCore.Qt.AlignRight or QtCore.Qt.AlignVCenter) + lineEdit.deselect() + + def on_model_rowsInserted(self, parent, start, end): + self.openPersistentEditor( + self.model.index(start, 0), + self.model.index(end, self.model.columnCount() - 1)) + + def updateLayerCombo(self): + layers = dataobjects.getVectorLayers() + layers.sort(key = lambda lay: lay.name()) + for layer in layers: + self.layerCombo.addItem(layer.name(), layer) + + @QtCore.pyqtSlot(bool, name='on_loadLayerFieldsButton_clicked') + def on_loadLayerFieldsButton_clicked(self, checked=False): + layer = self.layerCombo.itemData(self.layerCombo.currentIndex()) + if layer is None: + return + self.model.loadLayerFields(layer) diff --git a/python/plugins/processing/algs/qgis/ui/ui_widgetFieldsMapping.py b/python/plugins/processing/algs/qgis/ui/ui_widgetFieldsMapping.py new file mode 100644 index 00000000000..a507d29cd13 --- /dev/null +++ b/python/plugins/processing/algs/qgis/ui/ui_widgetFieldsMapping.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'widgetFieldsMapping.ui' +# +# Created: Tue Jan 20 10:14:41 2015 +# by: PyQt4 UI code generator 4.10.4 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(590, 552) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + self.verticalLayout_2 = QtGui.QVBoxLayout(Form) + self.verticalLayout_2.setMargin(0) + self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.fieldsView = QtGui.QTableView(Form) + self.fieldsView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) + self.fieldsView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.fieldsView.setObjectName(_fromUtf8("fieldsView")) + self.horizontalLayout.addWidget(self.fieldsView) + self.buttonLayout = QtGui.QVBoxLayout() + self.buttonLayout.setObjectName(_fromUtf8("buttonLayout")) + self.addButton = QtGui.QToolButton(Form) + self.addButton.setObjectName(_fromUtf8("addButton")) + self.buttonLayout.addWidget(self.addButton) + self.deleteButton = QtGui.QToolButton(Form) + self.deleteButton.setObjectName(_fromUtf8("deleteButton")) + self.buttonLayout.addWidget(self.deleteButton) + self.upButton = QtGui.QToolButton(Form) + self.upButton.setObjectName(_fromUtf8("upButton")) + self.buttonLayout.addWidget(self.upButton) + self.downButton = QtGui.QToolButton(Form) + self.downButton.setObjectName(_fromUtf8("downButton")) + self.buttonLayout.addWidget(self.downButton) + self.resetButton = QtGui.QToolButton(Form) + self.resetButton.setObjectName(_fromUtf8("resetButton")) + self.buttonLayout.addWidget(self.resetButton) + spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.buttonLayout.addItem(spacerItem) + self.horizontalLayout.addLayout(self.buttonLayout) + self.verticalLayout_2.addLayout(self.horizontalLayout) + self.loadFromLayerLayout = QtGui.QHBoxLayout() + self.loadFromLayerLayout.setObjectName(_fromUtf8("loadFromLayerLayout")) + self.loadFromLayerLabel = QtGui.QLabel(Form) + self.loadFromLayerLabel.setObjectName(_fromUtf8("loadFromLayerLabel")) + self.loadFromLayerLayout.addWidget(self.loadFromLayerLabel) + self.layerCombo = QtGui.QComboBox(Form) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.layerCombo.sizePolicy().hasHeightForWidth()) + self.layerCombo.setSizePolicy(sizePolicy) + self.layerCombo.setObjectName(_fromUtf8("layerCombo")) + self.loadFromLayerLayout.addWidget(self.layerCombo) + self.loadLayerFieldsButton = QtGui.QPushButton(Form) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.loadLayerFieldsButton.sizePolicy().hasHeightForWidth()) + self.loadLayerFieldsButton.setSizePolicy(sizePolicy) + self.loadLayerFieldsButton.setObjectName(_fromUtf8("loadLayerFieldsButton")) + self.loadFromLayerLayout.addWidget(self.loadLayerFieldsButton) + self.verticalLayout_2.addLayout(self.loadFromLayerLayout) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(_translate("Form", "Fields", None)) + self.addButton.setToolTip(_translate("Form", "Add new field", None)) + self.addButton.setText(_translate("Form", "add", None)) + self.deleteButton.setToolTip(_translate("Form", "Delete selected field", None)) + self.deleteButton.setText(_translate("Form", "delete", None)) + self.upButton.setToolTip(_translate("Form", "Move selected field up", None)) + self.upButton.setText(_translate("Form", "up", None)) + self.downButton.setToolTip(_translate("Form", "Move selected field down", None)) + self.downButton.setText(_translate("Form", "down", None)) + self.resetButton.setToolTip(_translate("Form", "Reset all fields", None)) + self.resetButton.setText(_translate("Form", "reset", None)) + self.loadFromLayerLabel.setText(_translate("Form", "Load fields from layer", None)) + self.loadLayerFieldsButton.setToolTip(_translate("Form", "Load fields from selected layer", None)) + self.loadLayerFieldsButton.setText(_translate("Form", "Load fields", None)) + diff --git a/python/plugins/processing/algs/qgis/ui/widgetFieldsMapping.ui b/python/plugins/processing/algs/qgis/ui/widgetFieldsMapping.ui new file mode 100644 index 00000000000..b4b4cbf33d5 --- /dev/null +++ b/python/plugins/processing/algs/qgis/ui/widgetFieldsMapping.ui @@ -0,0 +1,148 @@ + + + Form + + + + 0 + 0 + 590 + 552 + + + + + 0 + 0 + + + + Fields + + + + 0 + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + + + + + + Add new field + + + add + + + + + + + Delete selected field + + + delete + + + + + + + Move selected field up + + + up + + + + + + + Move selected field down + + + down + + + + + + + Reset all fields + + + reset + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + Load fields from layer + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + Load fields from selected layer + + + Load fields + + + + + + + + + +