diff --git a/python/gui/auto_generated/qgsfieldmappingmodel.sip.in b/python/gui/auto_generated/qgsfieldmappingmodel.sip.in index 1b6fc426733..9714023d5db 100644 --- a/python/gui/auto_generated/qgsfieldmappingmodel.sip.in +++ b/python/gui/auto_generated/qgsfieldmappingmodel.sip.in @@ -41,12 +41,12 @@ the mapping expression is editable. { QString originalName; QgsField field; - QgsExpression expression; + QString expression; }; QgsFieldMappingModel( const QgsFields &sourceFields = QgsFields(), const QgsFields &destinationFields = QgsFields(), - const QMap &expressions = QMap(), + const QMap &expressions = QMap(), QObject *parent = 0 ); %Docstring Constructs a QgsFieldMappingModel from a set of ``sourceFields`` @@ -54,11 +54,6 @@ and ``destinationFields``, initial values for the expressions can be optionally specified through ``expressions`` which is a map from the original field name to the corresponding expression. A ``parent`` object can be also specified. -%End - - QgsExpressionContextGenerator *contextGenerator() const; -%Docstring -Returns the context generator with the source fields %End bool destinationEditable() const; @@ -86,7 +81,7 @@ Returns a list of source fields Returns a list of Field objects representing the current status of the model %End - void appendField( const QgsField &field, const QgsExpression &expression = QgsExpression() ); + void appendField( const QgsField &field, const QString &expression = QString() ); %Docstring Appends a new ``field`` to the model, with an optional ``expression`` %End @@ -109,10 +104,15 @@ Moves up the field at ``index`` void setSourceFields( const QgsFields &sourceFields ); %Docstring Set source fields to ``sourceFields`` +%End + + QgsExpressionContextGenerator *contextGenerator() const; +%Docstring +Returns the context generator with the source fields %End void setDestinationFields( const QgsFields &destinationFields, - const QMap &expressions = QMap() ); + const QMap &expressions = QMap() ); %Docstring Set destination fields to ``destinationFields``, initial values for the expressions can be optionally specified through ``expressions`` which is a map from the original @@ -135,6 +135,8 @@ field name to the corresponding expression. public: }; + + /************************************************************************ * This file has been generated automatically from * * * diff --git a/python/gui/auto_generated/qgsfieldmappingwidget.sip.in b/python/gui/auto_generated/qgsfieldmappingwidget.sip.in index a5573c35e5f..f5736574624 100644 --- a/python/gui/auto_generated/qgsfieldmappingwidget.sip.in +++ b/python/gui/auto_generated/qgsfieldmappingwidget.sip.in @@ -26,7 +26,7 @@ for each set of "destination" fields an expression defines how to obtain the val explicit QgsFieldMappingWidget( QWidget *parent = 0, const QgsFields &sourceFields = QgsFields(), const QgsFields &destinationFields = QgsFields(), - const QMap &expressions = QMap() ); + const QMap &expressions = QMap() ); %Docstring Constructs a QgsFieldMappingWidget from a set of ``sourceFields`` and ``destinationFields``, initial values for the expressions can be @@ -66,7 +66,7 @@ Set source fields of the underlying mapping model to ``sourceFields`` %End void setDestinationFields( const QgsFields &destinationFields, - const QMap &expressions = QMap() ); + const QMap &expressions = QMap() ); %Docstring Set destination fields to ``destinationFields`` in the underlying model, initial values for the expressions can be optionally specified through @@ -81,7 +81,7 @@ Scroll the fields view to ``index`` public slots: - void appendField( const QgsField &field, const QgsExpression &expression = QgsExpression() ); + void appendField( const QgsField &field, const QString &expression = QString() ); %Docstring Appends a new ``field`` to the model, with an optional ``expression`` %End diff --git a/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py b/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py index c3ac85dd9d5..7587c926d67 100644 --- a/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py +++ b/python/plugins/processing/algs/qgis/ui/FieldsMappingPanel.py @@ -48,7 +48,6 @@ from qgis.core import ( QgsVectorLayer, QgsField, QgsFields, - QgsExpression, ) from processing.gui.wrappers import WidgetWrapper, DIALOG_STANDARD, DIALOG_MODELER @@ -110,7 +109,7 @@ class FieldsMappingPanel(BASE, WIDGET): 'type': f.field.type(), 'length': f.field.length(), 'precision': f.field.precision(), - 'expression': f.expression.expression(), + 'expression': f.expression, }) return results @@ -126,7 +125,7 @@ class FieldsMappingPanel(BASE, WIDGET): field_def.get('length', 0), field_def.get('precision', 0)) try: - expressions[f.name()] = QgsExpression(field_def['expressions']) + expressions[f.name()] = field_def['expressions'] except AttributeError: pass destinationFields.append(f) diff --git a/src/gui/qgsfieldmappingmodel.cpp b/src/gui/qgsfieldmappingmodel.cpp index 4b803743f9c..2b2fb321c0b 100644 --- a/src/gui/qgsfieldmappingmodel.cpp +++ b/src/gui/qgsfieldmappingmodel.cpp @@ -20,7 +20,7 @@ QgsFieldMappingModel::QgsFieldMappingModel( const QgsFields &sourceFields, const QgsFields &destinationFields, - const QMap &expressions, + const QMap &expressions, QObject *parent ) : QAbstractTableModel( parent ) , mSourceFields( sourceFields ) @@ -35,29 +35,29 @@ QVariant QgsFieldMappingModel::headerData( int section, Qt::Orientation orientat { if ( orientation == Qt::Horizontal ) { - switch ( section ) + switch ( static_cast( section ) ) { - case static_cast( ColumnDataIndex::SourceExpression ): + case ColumnDataIndex::SourceExpression: { return tr( "Source expression" ); } - case static_cast( ColumnDataIndex::DestinationName ): + case ColumnDataIndex::DestinationName: { return tr( "Name" ); } - case static_cast( ColumnDataIndex::DestinationType ): + case ColumnDataIndex::DestinationType: { return tr( "Type" ); } - case static_cast( ColumnDataIndex::DestinationLength ): + case ColumnDataIndex::DestinationLength: { return tr( "Length" ); } - case static_cast( ColumnDataIndex::DestinationPrecision ): + case ColumnDataIndex::DestinationPrecision: { return tr( "Precision" ); } - case static_cast( ColumnDataIndex::DestinationConstraints ): + case ColumnDataIndex::DestinationConstraints: { return tr( "Constraints" ); } @@ -78,13 +78,15 @@ QgsFields QgsFieldMappingModel::sourceFields() const int QgsFieldMappingModel::rowCount( const QModelIndex &parent ) const { - Q_UNUSED( parent ); + if ( parent.isValid() ) + return 0; return mMapping.count(); } int QgsFieldMappingModel::columnCount( const QModelIndex &parent ) const { - Q_UNUSED( parent ); + if ( parent.isValid() ) + return 0; return 6; } @@ -99,33 +101,31 @@ QVariant QgsFieldMappingModel::data( const QModelIndex &index, int role ) const if ( role == Qt::DisplayRole || role == Qt::EditRole ) { - switch ( col ) + switch ( static_cast( col ) ) { - { - case static_cast( ColumnDataIndex::SourceExpression ): - { - return f.expression.expression(); - } - case static_cast( ColumnDataIndex::DestinationName ): - { - return f.field.displayName(); - } - case static_cast( ColumnDataIndex::DestinationType ): - { - return static_cast( f.field.type() ); - } - case static_cast( ColumnDataIndex::DestinationLength ): - { - return f.field.length(); - } - case static_cast( ColumnDataIndex::DestinationPrecision ): - { - return f.field.precision(); - } - case static_cast( ColumnDataIndex::DestinationConstraints ): - { - return constraints != 0 ? tr( "Constraints active" ) : QString(); - } + case ColumnDataIndex::SourceExpression: + { + return f.expression; + } + case ColumnDataIndex::DestinationName: + { + return f.field.displayName(); + } + case ColumnDataIndex::DestinationType: + { + return static_cast( f.field.type() ); + } + case ColumnDataIndex::DestinationLength: + { + return f.field.length(); + } + case ColumnDataIndex::DestinationPrecision: + { + return f.field.precision(); + } + case ColumnDataIndex::DestinationConstraints: + { + return constraints != 0 ? tr( "Constraints active" ) : QString(); } } } @@ -157,28 +157,6 @@ QVariant QgsFieldMappingModel::data( const QModelIndex &index, int role ) const return QVariant(); } -QgsExpressionContextGenerator *QgsFieldMappingModel::contextGenerator() const -{ - return mExpressionContextGenerator.get(); -} - - -QgsFieldMappingModel::ExpressionContextGenerator::ExpressionContextGenerator( const QgsFields *sourceFields ) -{ - mSourceFields = sourceFields; -} - -QgsExpressionContext QgsFieldMappingModel::ExpressionContextGenerator::createExpressionContext() const -{ - QgsExpressionContext ctx; - ctx.appendScope( QgsExpressionContextUtils::globalScope() ); - ctx.setFields( *mSourceFields ); - QgsFeature feature { *mSourceFields }; - feature.setValid( true ); - ctx.setFeature( feature ); - return ctx; -} - Qt::ItemFlags QgsFieldMappingModel::flags( const QModelIndex &index ) const { if ( index.isValid() && @@ -199,25 +177,25 @@ bool QgsFieldMappingModel::setData( const QModelIndex &index, const QVariant &va if ( role == Qt::EditRole ) { Field &f = mMapping[index.row()]; - switch ( index.column() ) + switch ( static_cast( index.column() ) ) { - case static_cast( ColumnDataIndex::SourceExpression ): + case ColumnDataIndex::SourceExpression: { const QgsExpression exp { value.toString() }; f.expression = exp; break; } - case static_cast( ColumnDataIndex::DestinationName ): + case ColumnDataIndex::DestinationName: { f.field.setName( value.toString() ); break; } - case static_cast( ColumnDataIndex::DestinationType ): + case ColumnDataIndex::DestinationType: { f.field.setType( static_cast( value.toInt( ) ) ); break; } - case static_cast( ColumnDataIndex::DestinationLength ): + case ColumnDataIndex::DestinationLength: { bool ok; const int length { value.toInt( &ok ) }; @@ -225,7 +203,7 @@ bool QgsFieldMappingModel::setData( const QModelIndex &index, const QVariant &va f.field.setLength( length ); break; } - case static_cast( ColumnDataIndex::DestinationPrecision ): + case ColumnDataIndex::DestinationPrecision: { bool ok; const int precision { value.toInt( &ok ) }; @@ -233,11 +211,19 @@ bool QgsFieldMappingModel::setData( const QModelIndex &index, const QVariant &va f.field.setPrecision( precision ); break; } + case ColumnDataIndex::DestinationConstraints: + { + // Not editable: do nothing + } } emit dataChanged( index, index ); } + return true; + } + else + { + return false; } - return true; } QgsFieldConstraints::Constraints QgsFieldMappingModel::fieldConstraints( const QgsField &field ) const @@ -283,11 +269,11 @@ bool QgsFieldMappingModel::moveUpOrDown( const QModelIndex &index, bool up ) return true; } -QString QgsFieldMappingModel::bestMatchforField( const QgsFieldMappingModel::Field &f, QStringList &excludedFieldNames ) +QString QgsFieldMappingModel::findExpressionForDestinationField( const QgsFieldMappingModel::Field &f, QStringList &excludedFieldNames ) { // Search for fields in the source // 1. match by name - for ( const auto &sf : qgis::as_const( mSourceFields ) ) + for ( const QgsField &sf : qgis::as_const( mSourceFields ) ) { if ( sf.name() == f.field.name() ) { @@ -295,7 +281,7 @@ QString QgsFieldMappingModel::bestMatchforField( const QgsFieldMappingModel::Fie } } // 2. match by type - for ( const auto &sf : qgis::as_const( mSourceFields ) ) + for ( const QgsField &sf : qgis::as_const( mSourceFields ) ) { if ( excludedFieldNames.contains( sf.name() ) || sf.type() != f.field.type() ) continue; @@ -312,26 +298,30 @@ void QgsFieldMappingModel::setSourceFields( const QgsFields &sourceFields ) beginResetModel(); for ( const Field &f : qgis::as_const( mMapping ) ) { - if ( f.expression.isField() ) + if ( QgsExpression( f.expression ).isField() ) { - usedFields.push_back( f.expression.expression().mid( 1, f.expression.expression().length() - 2 ) ); + usedFields.push_back( f.expression.mid( 1, f.expression.length() - 2 ) ); } } - // not const on purpose - for ( Field &f : mMapping ) + for ( auto it = mMapping.begin(); it != mMapping.end(); ++it ) { - if ( f.expression.expression().isEmpty() ) + if ( it->expression.isEmpty() ) { - const QString expression { bestMatchforField( f, usedFields ) }; + const QString expression { findExpressionForDestinationField( *it, usedFields ) }; if ( ! expression.isEmpty() ) - f.expression = expression; + it->expression = expression; } } endResetModel(); } +QgsExpressionContextGenerator *QgsFieldMappingModel::contextGenerator() const +{ + return mExpressionContextGenerator.get(); +} + void QgsFieldMappingModel::setDestinationFields( const QgsFields &destinationFields, - const QMap &expressions ) + const QMap &expressions ) { beginResetModel(); mMapping.clear(); @@ -345,16 +335,17 @@ void QgsFieldMappingModel::setDestinationFields( const QgsFields &destinationFie if ( expressions.contains( f.field.name() ) ) { f.expression = expressions.value( f.field.name() ); + const QgsExpression exp { f.expression }; // if it's source field - if ( f.expression.isField() && - mSourceFields.names().contains( f.expression.referencedColumns().toList().first() ) ) + if ( exp.isField() && + mSourceFields.names().contains( exp.referencedColumns().toList().first() ) ) { - usedFields.push_back( f.expression.referencedColumns().toList().first() ); + usedFields.push_back( exp.referencedColumns().toList().first() ); } } else { - const QString expression { bestMatchforField( f, usedFields ) }; + const QString expression { findExpressionForDestinationField( f, usedFields ) }; if ( ! expression.isEmpty() ) f.expression = expression; } @@ -395,7 +386,7 @@ QList QgsFieldMappingModel::mapping() const return mMapping; } -void QgsFieldMappingModel::appendField( const QgsField &field, const QgsExpression &expression ) +void QgsFieldMappingModel::appendField( const QgsField &field, const QString &expression ) { const int lastRow { rowCount( QModelIndex( ) ) }; beginInsertRows( QModelIndex(), lastRow, lastRow ); @@ -432,3 +423,18 @@ bool QgsFieldMappingModel::moveDown( const QModelIndex &index ) return moveUpOrDown( index, false ); } +QgsFieldMappingModel::ExpressionContextGenerator::ExpressionContextGenerator( const QgsFields *sourceFields ) + : mSourceFields( sourceFields ) +{ +} + +QgsExpressionContext QgsFieldMappingModel::ExpressionContextGenerator::createExpressionContext() const +{ + QgsExpressionContext ctx; + ctx.appendScope( QgsExpressionContextUtils::globalScope() ); + ctx.setFields( *mSourceFields ); + QgsFeature feature { *mSourceFields }; + feature.setValid( true ); + ctx.setFeature( feature ); + return ctx; +} diff --git a/src/gui/qgsfieldmappingmodel.h b/src/gui/qgsfieldmappingmodel.h index 19d42ba693f..35a53ad6294 100644 --- a/src/gui/qgsfieldmappingmodel.h +++ b/src/gui/qgsfieldmappingmodel.h @@ -20,7 +20,6 @@ #include #include "qgsfields.h" -#include "qgsexpression.h" #include "qgsexpressioncontextgenerator.h" #include "qgsfieldconstraints.h" #include "qgis_gui.h" @@ -68,7 +67,7 @@ class GUI_EXPORT QgsFieldMappingModel: public QAbstractTableModel //! The field in its current status (it might have been renamed) QgsField field; //! The expression for the mapped field from the source fields - QgsExpression expression; + QString expression; }; /** @@ -80,12 +79,9 @@ class GUI_EXPORT QgsFieldMappingModel: public QAbstractTableModel */ QgsFieldMappingModel( const QgsFields &sourceFields = QgsFields(), const QgsFields &destinationFields = QgsFields(), - const QMap &expressions = QMap(), + const QMap &expressions = QMap(), QObject *parent = nullptr ); - //! Returns the context generator with the source fields - QgsExpressionContextGenerator *contextGenerator() const; - //! Returns TRUE if the destination fields are editable bool destinationEditable() const; @@ -102,7 +98,7 @@ class GUI_EXPORT QgsFieldMappingModel: public QAbstractTableModel QList mapping() const; //! Appends a new \a field to the model, with an optional \a expression - void appendField( const QgsField &field, const QgsExpression &expression = QgsExpression() ); + void appendField( const QgsField &field, const QString &expression = QString() ); //! Removes the field at \a index from the model, returns TRUE on success bool removeField( const QModelIndex &index ); @@ -116,13 +112,16 @@ class GUI_EXPORT QgsFieldMappingModel: public QAbstractTableModel //! Set source fields to \a sourceFields void setSourceFields( const QgsFields &sourceFields ); + //! Returns the context generator with the source fields + QgsExpressionContextGenerator *contextGenerator() const; + /** * Set destination fields to \a destinationFields, initial values for the expressions can be * optionally specified through \a expressions which is a map from the original * field name to the corresponding expression. */ void setDestinationFields( const QgsFields &destinationFields, - const QMap &expressions = QMap() ); + const QMap &expressions = QMap() ); // QAbstractItemModel interface int rowCount( const QModelIndex &parent = QModelIndex() ) const override; @@ -150,16 +149,27 @@ class GUI_EXPORT QgsFieldMappingModel: public QAbstractTableModel }; + QgsFieldConstraints::Constraints fieldConstraints( const QgsField &field ) const; bool moveUpOrDown( const QModelIndex &index, bool up = true ); - QString bestMatchforField( const QgsFieldMappingModel::Field &field, QStringList &excludedFieldNames ); + /** + * Try to find the best expression for a destination \a field by searching in the + * source fields for fields with: + * - the same name + * - the same type + * Returns an expression containing a reference to the field that matches first. + */ + QString findExpressionForDestinationField( const QgsFieldMappingModel::Field &field, QStringList &excludedFieldNames ); QList mMapping; bool mDestinationEditable = false; QgsFields mSourceFields; std::unique_ptr mExpressionContextGenerator; + }; + + #endif // QGSFIELDMAPPINGMODEL_H diff --git a/src/gui/qgsfieldmappingwidget.cpp b/src/gui/qgsfieldmappingwidget.cpp index 3f4be08d8c1..703cc9f7298 100644 --- a/src/gui/qgsfieldmappingwidget.cpp +++ b/src/gui/qgsfieldmappingwidget.cpp @@ -16,17 +16,27 @@ #include "qgsfieldmappingwidget.h" #include "qgsfieldexpressionwidget.h" +#include "qgsexpression.h" + +#ifdef ENABLE_MODELTEST +#include "modeltest.h" +#endif QgsFieldMappingWidget::QgsFieldMappingWidget( QWidget *parent, const QgsFields &sourceFields, const QgsFields &destinationFields, - const QMap &expressions ) + const QMap &expressions ) : QWidget( parent ) { setupUi( this ); mModel = new QgsFieldMappingModel( sourceFields, destinationFields, expressions, this ); + +#ifdef ENABLE_MODELTEST + new ModelTest( mModel, this ); +#endif + mTableView->setModel( mModel ); mTableView->setItemDelegateForColumn( static_cast( QgsFieldMappingModel::ColumnDataIndex::SourceExpression ), new ExpressionDelegate( mTableView ) ); mTableView->setItemDelegateForColumn( static_cast( QgsFieldMappingModel::ColumnDataIndex::DestinationType ), new TypeDelegate( mTableView ) ); @@ -67,7 +77,7 @@ void QgsFieldMappingWidget::setSourceFields( const QgsFields &sourceFields ) model()->setSourceFields( sourceFields ); } -void QgsFieldMappingWidget::setDestinationFields( const QgsFields &destinationFields, const QMap &expressions ) +void QgsFieldMappingWidget::setDestinationFields( const QgsFields &destinationFields, const QMap &expressions ) { model()->setDestinationFields( destinationFields, expressions ); } @@ -77,7 +87,7 @@ void QgsFieldMappingWidget::scrollTo( const QModelIndex &index ) const mTableView->scrollTo( index ); } -void QgsFieldMappingWidget::appendField( const QgsField &field, const QgsExpression &expression ) +void QgsFieldMappingWidget::appendField( const QgsField &field, const QString &expression ) { model()->appendField( field, expression ); } diff --git a/src/gui/qgsfieldmappingwidget.h b/src/gui/qgsfieldmappingwidget.h index f866f723915..dab0416efa0 100644 --- a/src/gui/qgsfieldmappingwidget.h +++ b/src/gui/qgsfieldmappingwidget.h @@ -47,7 +47,7 @@ class GUI_EXPORT QgsFieldMappingWidget : public QWidget, private Ui::QgsFieldMap explicit QgsFieldMappingWidget( QWidget *parent = nullptr, const QgsFields &sourceFields = QgsFields(), const QgsFields &destinationFields = QgsFields(), - const QMap &expressions = QMap() ); + const QMap &expressions = QMap() ); //! Sets the destination fields editable state to \a editable void setDestinationEditable( bool editable ); @@ -74,7 +74,7 @@ class GUI_EXPORT QgsFieldMappingWidget : public QWidget, private Ui::QgsFieldMap * corresponding expression. */ void setDestinationFields( const QgsFields &destinationFields, - const QMap &expressions = QMap() ); + const QMap &expressions = QMap() ); /** * Scroll the fields view to \a index @@ -84,7 +84,7 @@ class GUI_EXPORT QgsFieldMappingWidget : public QWidget, private Ui::QgsFieldMap public slots: //! Appends a new \a field to the model, with an optional \a expression - void appendField( const QgsField &field, const QgsExpression &expression = QgsExpression() ); + void appendField( const QgsField &field, const QString &expression = QString() ); //! Removes the currently selected field from the model bool removeSelectedFields( );