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