1 /***************************************************************************
2   qgsfieldmappingmodel.cpp - QgsFieldMappingModel
3 
4  ---------------------
5  begin                : 17.3.2020
6  copyright            : (C) 2020 by Alessandro Pasotti
7  email                : elpaso at itopen dot it
8  ***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 
17 #include "qgsfieldmappingmodel.h"
18 #include "qgsexpressioncontextutils.h"
19 #include "qgsexpressionnodeimpl.h"
20 
QgsFieldMappingModel(const QgsFields & sourceFields,const QgsFields & destinationFields,const QMap<QString,QString> & expressions,QObject * parent)21 QgsFieldMappingModel::QgsFieldMappingModel( const QgsFields &sourceFields,
22     const QgsFields &destinationFields,
23     const QMap<QString, QString> &expressions,
24     QObject *parent )
25   : QAbstractTableModel( parent )
26   , mSourceFields( sourceFields )
27   , mExpressionContextGenerator( new ExpressionContextGenerator( mSourceFields ) )
28 {
29   setDestinationFields( destinationFields, expressions );
30 }
31 
headerData(int section,Qt::Orientation orientation,int role) const32 QVariant QgsFieldMappingModel::headerData( int section, Qt::Orientation orientation, int role ) const
33 {
34   if ( role == Qt::DisplayRole )
35   {
36     switch ( orientation )
37     {
38       case Qt::Horizontal:
39       {
40         switch ( static_cast<ColumnDataIndex>( section ) )
41         {
42           case ColumnDataIndex::SourceExpression:
43           {
44             return tr( "Source Expression" );
45           }
46           case ColumnDataIndex::DestinationName:
47           {
48             return tr( "Name" );
49           }
50           case ColumnDataIndex::DestinationType:
51           {
52             return tr( "Type" );
53           }
54           case ColumnDataIndex::DestinationLength:
55           {
56             return tr( "Length" );
57           }
58           case ColumnDataIndex::DestinationPrecision:
59           {
60             return tr( "Precision" );
61           }
62           case ColumnDataIndex::DestinationConstraints:
63           {
64             return tr( "Constraints" );
65           }
66         }
67         break;
68       }
69       case Qt::Vertical:
70       {
71         return section;
72       }
73     }
74   }
75   return QVariant();
76 }
77 
sourceFields() const78 QgsFields QgsFieldMappingModel::sourceFields() const
79 {
80   return mSourceFields;
81 }
82 
rowCount(const QModelIndex & parent) const83 int QgsFieldMappingModel::rowCount( const QModelIndex &parent ) const
84 {
85   if ( parent.isValid() )
86     return 0;
87   return mMapping.count();
88 }
89 
columnCount(const QModelIndex & parent) const90 int QgsFieldMappingModel::columnCount( const QModelIndex &parent ) const
91 {
92   if ( parent.isValid() )
93     return 0;
94   return 6;
95 }
96 
data(const QModelIndex & index,int role) const97 QVariant QgsFieldMappingModel::data( const QModelIndex &index, int role ) const
98 {
99   if ( index.isValid() )
100   {
101     const ColumnDataIndex col { static_cast<ColumnDataIndex>( index.column() ) };
102     const Field &f { mMapping.at( index.row() ) };
103 
104     const QgsFieldConstraints::Constraints constraints { fieldConstraints( f.field ) };
105 
106     switch ( role )
107     {
108       case Qt::DisplayRole:
109       case Qt::EditRole:
110       {
111         switch ( col )
112         {
113           case ColumnDataIndex::SourceExpression:
114           {
115             return f.expression;
116           }
117           case ColumnDataIndex::DestinationName:
118           {
119             return f.field.displayName();
120           }
121           case ColumnDataIndex::DestinationType:
122           {
123             return static_cast<int>( f.field.type() );
124           }
125           case ColumnDataIndex::DestinationLength:
126           {
127             return f.field.length();
128           }
129           case ColumnDataIndex::DestinationPrecision:
130           {
131             return f.field.precision();
132           }
133           case ColumnDataIndex::DestinationConstraints:
134           {
135             return constraints != 0 ? tr( "Constraints active" ) : QString();
136           }
137         }
138         break;
139       }
140       case Qt::ToolTipRole:
141       {
142         if ( col == ColumnDataIndex::DestinationConstraints &&
143              constraints != 0 )
144         {
145           QStringList constraintDescription;
146           if ( constraints.testFlag( QgsFieldConstraints::Constraint::ConstraintUnique ) )
147           {
148             constraintDescription.push_back( tr( "Unique" ) );
149           }
150           if ( constraints.testFlag( QgsFieldConstraints::Constraint::ConstraintNotNull ) )
151           {
152             constraintDescription.push_back( tr( "Not null" ) );
153           }
154           if ( constraints.testFlag( QgsFieldConstraints::Constraint::ConstraintExpression ) )
155           {
156             constraintDescription.push_back( tr( "Expression" ) );
157           }
158           return constraintDescription.join( QLatin1String( "<br>" ) );
159         }
160         break;
161       }
162       case Qt::BackgroundRole:
163       {
164         if ( constraints != 0 )
165         {
166           return QBrush( QColor( 255, 224, 178 ) );
167         }
168         break;
169       }
170     }
171   }
172   return QVariant();
173 }
174 
flags(const QModelIndex & index) const175 Qt::ItemFlags QgsFieldMappingModel::flags( const QModelIndex &index ) const
176 {
177   if ( index.isValid() &&
178        index.column() != static_cast<int>( ColumnDataIndex::DestinationConstraints ) &&
179        ( index.column() == static_cast<int>( ColumnDataIndex::SourceExpression ) || destinationEditable() ) )
180   {
181     return Qt::ItemFlags( Qt::ItemIsSelectable |
182                           Qt::ItemIsEditable |
183                           Qt::ItemIsEnabled );
184   }
185   return Qt::ItemFlags();
186 }
187 
setData(const QModelIndex & index,const QVariant & value,int role)188 bool QgsFieldMappingModel::setData( const QModelIndex &index, const QVariant &value, int role )
189 {
190   if ( index.isValid() )
191   {
192     if ( role == Qt::EditRole )
193     {
194       Field &f = mMapping[index.row()];
195       switch ( static_cast<ColumnDataIndex>( index.column() ) )
196       {
197         case ColumnDataIndex::SourceExpression:
198         {
199           const QgsExpression exp { value.toString() };
200           f.expression = exp;
201           break;
202         }
203         case ColumnDataIndex::DestinationName:
204         {
205           f.field.setName( value.toString() );
206           break;
207         }
208         case ColumnDataIndex::DestinationType:
209         {
210           f.field.setType( static_cast<QVariant::Type>( value.toInt( ) ) );
211           break;
212         }
213         case ColumnDataIndex::DestinationLength:
214         {
215           bool ok;
216           const int length { value.toInt( &ok ) };
217           if ( ok )
218             f.field.setLength( length );
219           break;
220         }
221         case ColumnDataIndex::DestinationPrecision:
222         {
223           bool ok;
224           const int precision { value.toInt( &ok ) };
225           if ( ok )
226             f.field.setPrecision( precision );
227           break;
228         }
229         case ColumnDataIndex::DestinationConstraints:
230         {
231           // Not editable: do nothing
232         }
233       }
234       emit dataChanged( index, index );
235     }
236     return true;
237   }
238   else
239   {
240     return false;
241   }
242 }
243 
fieldConstraints(const QgsField & field) const244 QgsFieldConstraints::Constraints QgsFieldMappingModel::fieldConstraints( const QgsField &field ) const
245 {
246   QgsFieldConstraints::Constraints constraints;
247 
248   const QgsFieldConstraints fieldConstraints { field.constraints() };
249 
250   if ( fieldConstraints.constraints() & QgsFieldConstraints::ConstraintNotNull &&
251        fieldConstraints.constraintStrength( QgsFieldConstraints::ConstraintNotNull ) & QgsFieldConstraints::ConstraintStrengthHard )
252     constraints.setFlag( QgsFieldConstraints::ConstraintNotNull );
253 
254   if ( fieldConstraints.constraints() & QgsFieldConstraints::ConstraintUnique &&
255        fieldConstraints.constraintStrength( QgsFieldConstraints::ConstraintUnique ) & QgsFieldConstraints::ConstraintStrengthHard )
256     constraints.setFlag( QgsFieldConstraints::ConstraintUnique );
257 
258   if ( fieldConstraints.constraints() & QgsFieldConstraints::ConstraintExpression &&
259        fieldConstraints.constraintStrength( QgsFieldConstraints::ConstraintExpression ) & QgsFieldConstraints::ConstraintStrengthHard )
260     constraints.setFlag( QgsFieldConstraints::ConstraintExpression );
261 
262   return constraints;
263 }
264 
moveUpOrDown(const QModelIndex & index,bool up)265 bool QgsFieldMappingModel::moveUpOrDown( const QModelIndex &index, bool up )
266 {
267   if ( ! index.isValid() && index.model() == this )
268     return false;
269 
270   // Always swap down
271   const int row { up ? index.row() - 1 : index.row() };
272   // Range checking
273   if ( row < 0 || row + 1 >= rowCount( QModelIndex() ) )
274   {
275     return false;
276   }
277   beginMoveRows( QModelIndex( ), row, row, QModelIndex(), row + 2 );
278 #if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
279   mMapping.swap( row, row + 1 );
280 #else
281   mMapping.swapItemsAt( row, row + 1 );
282 #endif
283   endMoveRows();
284   return true;
285 }
286 
findExpressionForDestinationField(const QgsFieldMappingModel::Field & f,QStringList & excludedFieldNames)287 QString QgsFieldMappingModel::findExpressionForDestinationField( const QgsFieldMappingModel::Field &f, QStringList &excludedFieldNames )
288 {
289   // Search for fields in the source
290   // 1. match by name
291   for ( const QgsField &sf : qgis::as_const( mSourceFields ) )
292   {
293     if ( sf.name() == f.field.name() )
294     {
295       excludedFieldNames.push_back( sf.name() );
296       return QgsExpression::quotedColumnRef( sf.name() );
297     }
298   }
299   // 2. match by type
300   for ( const QgsField &sf : qgis::as_const( mSourceFields ) )
301   {
302     if ( excludedFieldNames.contains( sf.name() ) || sf.type() != f.field.type() )
303       continue;
304     excludedFieldNames.push_back( sf.name() );
305     return QgsExpression::quotedColumnRef( sf.name() );
306   }
307   return QString();
308 }
309 
setSourceFields(const QgsFields & sourceFields)310 void QgsFieldMappingModel::setSourceFields( const QgsFields &sourceFields )
311 {
312   mSourceFields = sourceFields;
313   if ( mExpressionContextGenerator )
314     mExpressionContextGenerator->setSourceFields( mSourceFields );
315   QStringList usedFields;
316   beginResetModel();
317   for ( const Field &f : qgis::as_const( mMapping ) )
318   {
319     if ( QgsExpression( f.expression ).isField() )
320     {
321       usedFields.push_back( f.expression.mid( 1, f.expression.length() - 2 ) );
322     }
323   }
324   for ( auto it = mMapping.begin(); it != mMapping.end(); ++it )
325   {
326     if ( it->expression.isEmpty() )
327     {
328       const QString expression { findExpressionForDestinationField( *it, usedFields ) };
329       if ( ! expression.isEmpty() )
330         it->expression = expression;
331     }
332   }
333   endResetModel();
334 }
335 
contextGenerator() const336 QgsExpressionContextGenerator *QgsFieldMappingModel::contextGenerator() const
337 {
338   return mExpressionContextGenerator.get();
339 }
340 
setBaseExpressionContextGenerator(const QgsExpressionContextGenerator * generator)341 void QgsFieldMappingModel::setBaseExpressionContextGenerator( const QgsExpressionContextGenerator *generator )
342 {
343   mExpressionContextGenerator->setBaseExpressionContextGenerator( generator );
344 }
345 
setDestinationFields(const QgsFields & destinationFields,const QMap<QString,QString> & expressions)346 void QgsFieldMappingModel::setDestinationFields( const QgsFields &destinationFields,
347     const QMap<QString, QString> &expressions )
348 {
349   beginResetModel();
350   mMapping.clear();
351   // Prepare the model data
352   QStringList usedFields;
353   for ( const QgsField &df : destinationFields )
354   {
355     Field f;
356     f.field = df;
357     f.originalName = df.name();
358     if ( expressions.contains( f.field.name() ) )
359     {
360       f.expression = expressions.value( f.field.name() );
361       const QgsExpression exp { f.expression };
362       // if it's source field
363       if ( exp.isField() &&
364            mSourceFields.names().contains( qgis::setToList( exp.referencedColumns() ).first() ) )
365       {
366         usedFields.push_back( qgis::setToList( exp.referencedColumns() ).first() );
367       }
368     }
369     else
370     {
371       const QString expression { findExpressionForDestinationField( f, usedFields ) };
372       if ( ! expression.isEmpty() )
373         f.expression = expression;
374     }
375     mMapping.push_back( f );
376   }
377   endResetModel();
378 }
379 
destinationEditable() const380 bool QgsFieldMappingModel::destinationEditable() const
381 {
382   return mDestinationEditable;
383 }
384 
setDestinationEditable(bool destinationEditable)385 void QgsFieldMappingModel::setDestinationEditable( bool destinationEditable )
386 {
387   mDestinationEditable = destinationEditable;
388 }
389 
dataTypes()390 const QMap<QVariant::Type, QString> QgsFieldMappingModel::dataTypes()
391 {
392   static const QMap<QVariant::Type, QString> sDataTypes
393   {
394     { QVariant::Type::Int, tr( "Whole number (integer - 32bit)" ) },
395     { QVariant::Type::LongLong, tr( "Whole number (integer - 64bit)" ) },
396     { QVariant::Type::Double, tr( "Decimal number (double)" ) },
397     { QVariant::Type::String, tr( "Text (string)" ) },
398     { QVariant::Type::Date, tr( "Date" ) },
399     { QVariant::Type::Time, tr( "Time" ) },
400     { QVariant::Type::DateTime, tr( "Date & Time" ) },
401     { QVariant::Type::Bool, tr( "Boolean" ) },
402     { QVariant::Type::ByteArray, tr( "Binary object (BLOB)" ) },
403   };
404   return sDataTypes;
405 }
406 
mapping() const407 QList<QgsFieldMappingModel::Field> QgsFieldMappingModel::mapping() const
408 {
409   return mMapping;
410 }
411 
fieldPropertyMap() const412 QMap<QString, QgsProperty> QgsFieldMappingModel::fieldPropertyMap() const
413 {
414   QMap< QString, QgsProperty > fieldMap;
415   for ( const QgsFieldMappingModel::Field &field : mMapping )
416   {
417     const QgsExpression exp( field.expression );
418     const bool isField = exp.isField();
419     fieldMap.insert( field.originalName, isField
420                      ? QgsProperty::fromField( static_cast<const QgsExpressionNodeColumnRef *>( exp.rootNode() )->name() )
421                      : QgsProperty::fromExpression( field.expression ) );
422   }
423   return fieldMap;
424 }
425 
setFieldPropertyMap(const QMap<QString,QgsProperty> & map)426 void QgsFieldMappingModel::setFieldPropertyMap( const QMap<QString, QgsProperty> &map )
427 {
428   beginResetModel();
429   for ( int i = 0; i < mMapping.count(); ++i )
430   {
431     Field &f = mMapping[i];
432     if ( map.contains( f.field.name() ) )
433     {
434       const QgsProperty prop = map.value( f.field.name() );
435       switch ( prop.propertyType() )
436       {
437         case QgsProperty::StaticProperty:
438           f.expression = QgsExpression::quotedValue( prop.staticValue() );
439           break;
440 
441         case QgsProperty::FieldBasedProperty:
442           f.expression = prop.field();
443           break;
444 
445         case QgsProperty::ExpressionBasedProperty:
446           f.expression = prop.expressionString();
447           break;
448 
449         case QgsProperty::InvalidProperty:
450           f.expression.clear();
451           break;
452       }
453     }
454     else
455     {
456       f.expression.clear();
457     }
458   }
459   endResetModel();
460 }
461 
appendField(const QgsField & field,const QString & expression)462 void QgsFieldMappingModel::appendField( const QgsField &field, const QString &expression )
463 {
464   const int lastRow { rowCount( QModelIndex( ) ) };
465   beginInsertRows( QModelIndex(), lastRow, lastRow );
466   Field f;
467   f.field = field;
468   f.expression = expression;
469   f.originalName = field.name();
470   mMapping.push_back( f );
471   endInsertRows( );
472 }
473 
removeField(const QModelIndex & index)474 bool QgsFieldMappingModel::removeField( const QModelIndex &index )
475 {
476   if ( index.isValid() && index.model() == this && index.row() < rowCount( QModelIndex() ) )
477   {
478     beginRemoveRows( QModelIndex(), index.row(), index.row() );
479     mMapping.removeAt( index.row() );
480     endRemoveRows();
481     return true;
482   }
483   else
484   {
485     return false;
486   }
487 }
488 
moveUp(const QModelIndex & index)489 bool QgsFieldMappingModel::moveUp( const QModelIndex &index )
490 {
491   return moveUpOrDown( index );
492 }
493 
moveDown(const QModelIndex & index)494 bool QgsFieldMappingModel::moveDown( const QModelIndex &index )
495 {
496   return moveUpOrDown( index, false );
497 }
498 
ExpressionContextGenerator(const QgsFields & sourceFields)499 QgsFieldMappingModel::ExpressionContextGenerator::ExpressionContextGenerator( const QgsFields &sourceFields )
500   : mSourceFields( sourceFields )
501 {
502 }
503 
createExpressionContext() const504 QgsExpressionContext QgsFieldMappingModel::ExpressionContextGenerator::createExpressionContext() const
505 {
506   if ( mBaseGenerator )
507   {
508     QgsExpressionContext ctx = mBaseGenerator->createExpressionContext();
509     std::unique_ptr< QgsExpressionContextScope > fieldMappingScope = qgis::make_unique< QgsExpressionContextScope >( tr( "Field Mapping" ) );
510     fieldMappingScope->setFields( mSourceFields );
511     ctx.appendScope( fieldMappingScope.release() );
512     return ctx;
513   }
514   else
515   {
516     QgsExpressionContext ctx;
517     ctx.appendScope( QgsExpressionContextUtils::globalScope() );
518     ctx.setFields( mSourceFields );
519     QgsFeature feature { mSourceFields };
520     feature.setValid( true );
521     ctx.setFeature( feature );
522     return ctx;
523   }
524 }
525 
setBaseExpressionContextGenerator(const QgsExpressionContextGenerator * generator)526 void QgsFieldMappingModel::ExpressionContextGenerator::setBaseExpressionContextGenerator( const QgsExpressionContextGenerator *generator )
527 {
528   mBaseGenerator = generator;
529 }
530 
setSourceFields(const QgsFields & fields)531 void QgsFieldMappingModel::ExpressionContextGenerator::setSourceFields( const QgsFields &fields )
532 {
533   mSourceFields = fields;
534 }
535