QGIS API Documentation  3.25.0-Master (6b426f5f8a)
qgsprocessingaggregatewidgets.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsprocessingaggregatewidgets.cpp
3  ---------------------
4  Date : June 2020
5  Copyright : (C) 2020 by Nyall Dawson
6  Email : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
19 #include "qgsfieldmappingwidget.h"
20 
21 #include <QBoxLayout>
22 #include <QLineEdit>
23 #include <QMessageBox>
24 #include <QPushButton>
25 #include <QStandardItemModel>
26 #include <QToolButton>
27 #include <QTableView>
28 #include <mutex>
29 
30 //
31 // QgsAggregateMappingModel
32 //
33 
35  QObject *parent )
36  : QAbstractTableModel( parent )
37  , mExpressionContextGenerator( new QgsFieldMappingModel::ExpressionContextGenerator( sourceFields ) )
38 {
40 }
41 
42 QVariant QgsAggregateMappingModel::headerData( int section, Qt::Orientation orientation, int role ) const
43 {
44  if ( role == Qt::DisplayRole )
45  {
46  switch ( orientation )
47  {
48  case Qt::Horizontal:
49  {
50  switch ( static_cast<ColumnDataIndex>( section ) )
51  {
53  {
54  return tr( "Source Expression" );
55  }
57  {
58  return tr( "Aggregate Function" );
59  }
61  {
62  return tr( "Delimiter" );
63  }
65  {
66  return tr( "Name" );
67  }
69  {
70  return tr( "Type" );
71  }
73  {
74  return tr( "Length" );
75  }
77  {
78  return tr( "Precision" );
79  }
80  }
81  break;
82  }
83  case Qt::Vertical:
84  {
85  return section;
86  }
87  }
88  }
89  return QVariant();
90 }
91 
93 {
94  return mSourceFields;
95 }
96 
97 int QgsAggregateMappingModel::rowCount( const QModelIndex &parent ) const
98 {
99  if ( parent.isValid() )
100  return 0;
101  return mMapping.count();
102 }
103 
104 int QgsAggregateMappingModel::columnCount( const QModelIndex &parent ) const
105 {
106  if ( parent.isValid() )
107  return 0;
108  return 7;
109 }
110 
111 QVariant QgsAggregateMappingModel::data( const QModelIndex &index, int role ) const
112 {
113  if ( index.isValid() )
114  {
115  const ColumnDataIndex col { static_cast<ColumnDataIndex>( index.column() ) };
116  const Aggregate &agg { mMapping.at( index.row() ) };
117 
118  switch ( role )
119  {
120  case Qt::DisplayRole:
121  case Qt::EditRole:
122  {
123  switch ( col )
124  {
126  {
127  return agg.source;
128  }
130  {
131  return agg.aggregate;
132  }
134  {
135  return agg.delimiter;
136  }
138  {
139  return agg.field.displayName();
140  }
142  {
143  return agg.field.typeName();
144  }
146  {
147  return agg.field.length();
148  }
150  {
151  return agg.field.precision();
152  }
153  }
154  break;
155  }
156  }
157  }
158  return QVariant();
159 }
160 
161 Qt::ItemFlags QgsAggregateMappingModel::flags( const QModelIndex &index ) const
162 {
163  if ( index.isValid() )
164  {
165  return Qt::ItemFlags( Qt::ItemIsSelectable |
166  Qt::ItemIsEditable |
167  Qt::ItemIsEnabled );
168  }
169  return Qt::ItemFlags();
170 }
171 
172 bool QgsAggregateMappingModel::setData( const QModelIndex &index, const QVariant &value, int role )
173 {
174  if ( index.isValid() )
175  {
176  if ( role == Qt::EditRole )
177  {
178  Aggregate &f = mMapping[index.row()];
179  switch ( static_cast<ColumnDataIndex>( index.column() ) )
180  {
182  {
183  const QgsExpression exp { value.toString() };
184  f.source = exp;
185  break;
186  }
188  {
189  f.aggregate = value.toString();
190  break;
191  }
193  {
194  f.delimiter = value.toString();
195  break;
196  }
198  {
199  f.field.setName( value.toString() );
200  break;
201  }
203  {
204  QgsFieldMappingModel::setFieldTypeFromName( f.field, value.toString() );
205  break;
206  }
208  {
209  bool ok;
210  const int length { value.toInt( &ok ) };
211  if ( ok )
212  f.field.setLength( length );
213  break;
214  }
216  {
217  bool ok;
218  const int precision { value.toInt( &ok ) };
219  if ( ok )
221  break;
222  }
223  }
224  emit dataChanged( index, index );
225  }
226  return true;
227  }
228  else
229  {
230  return false;
231  }
232 }
233 
234 
235 bool QgsAggregateMappingModel::moveUpOrDown( const QModelIndex &index, bool up )
236 {
237  if ( ! index.isValid() && index.model() == this )
238  return false;
239 
240  // Always swap down
241  const int row { up ? index.row() - 1 : index.row() };
242  // Range checking
243  if ( row < 0 || row + 1 >= rowCount( QModelIndex() ) )
244  {
245  return false;
246  }
247  beginMoveRows( QModelIndex( ), row, row, QModelIndex(), row + 2 );
248 #if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
249  mMapping.swap( row, row + 1 );
250 #else
251  mMapping.swapItemsAt( row, row + 1 );
252 #endif
253  endMoveRows();
254  return true;
255 }
256 
258 {
259  mSourceFields = sourceFields;
260  if ( mExpressionContextGenerator )
261  mExpressionContextGenerator->setSourceFields( mSourceFields );
262 
263  const QStringList usedFields;
264  beginResetModel();
265  mMapping.clear();
266 
267  for ( const QgsField &f : sourceFields )
268  {
269  Aggregate aggregate;
270  aggregate.field = f;
271  aggregate.field.setTypeName( QgsFieldMappingModel::qgsFieldToTypeName( f ) );
272  aggregate.source = QgsExpression::quotedColumnRef( f.name() );
273 
274  if ( f.isNumeric() )
275  aggregate.aggregate = QStringLiteral( "sum" );
276  else if ( f.type() == QVariant::String || ( f.type() == QVariant::List && f.subType() == QVariant::String ) )
277  aggregate.aggregate = QStringLiteral( "concatenate" );
278 
279  aggregate.delimiter = ',';
280 
281  mMapping.push_back( aggregate );
282  }
283  endResetModel();
284 }
285 
287 {
288  return mExpressionContextGenerator.get();
289 }
290 
292 {
293  mExpressionContextGenerator->setBaseExpressionContextGenerator( generator );
294 }
295 
296 QList<QgsAggregateMappingModel::Aggregate> QgsAggregateMappingModel::mapping() const
297 {
298  return mMapping;
299 }
300 
301 void QgsAggregateMappingModel::setMapping( const QList<QgsAggregateMappingModel::Aggregate> &mapping )
302 {
303  beginResetModel();
304  mMapping = mapping;
305  for ( auto &agg : mMapping )
306  {
307  agg.field.setTypeName( QgsFieldMappingModel::qgsFieldToTypeName( agg.field ) );
308  }
309  endResetModel();
310 }
311 
312 void QgsAggregateMappingModel::appendField( const QgsField &field, const QString &source, const QString &aggregate )
313 {
314  const int lastRow { rowCount( QModelIndex( ) ) };
315  beginInsertRows( QModelIndex(), lastRow, lastRow );
316  Aggregate agg;
317  agg.field = field;
318  agg.field.setTypeName( QgsFieldMappingModel::qgsFieldToTypeName( field ) );
319  agg.source = source;
320  agg.aggregate = aggregate;
321  agg.delimiter = ',';
322  mMapping.push_back( agg );
323  endInsertRows( );
324 }
325 
326 bool QgsAggregateMappingModel::removeField( const QModelIndex &index )
327 {
328  if ( index.isValid() && index.model() == this && index.row() < rowCount( QModelIndex() ) )
329  {
330  beginRemoveRows( QModelIndex(), index.row(), index.row() );
331  mMapping.removeAt( index.row() );
332  endRemoveRows();
333  return true;
334  }
335  else
336  {
337  return false;
338  }
339 }
340 
341 bool QgsAggregateMappingModel::moveUp( const QModelIndex &index )
342 {
343  return moveUpOrDown( index );
344 }
345 
346 bool QgsAggregateMappingModel::moveDown( const QModelIndex &index )
347 {
348  return moveUpOrDown( index, false );
349 }
350 
351 
352 //
353 // QgsAggregateMappingWidget
354 //
355 
357  const QgsFields &sourceFields )
358  : QgsPanelWidget( parent )
359 {
360  QVBoxLayout *verticalLayout = new QVBoxLayout();
361  verticalLayout->setContentsMargins( 0, 0, 0, 0 );
362  mTableView = new QTableView();
363  verticalLayout->addWidget( mTableView );
364  setLayout( verticalLayout );
365 
366  mModel = new QgsAggregateMappingModel( sourceFields, this );
367  mTableView->setModel( mModel );
368  mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::SourceExpression ), new QgsFieldMappingWidget::ExpressionDelegate( this ) );
369  mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::Aggregate ), new QgsAggregateMappingWidget::AggregateDelegate( mTableView ) );
370  mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::DestinationType ), new QgsFieldMappingWidget::TypeDelegate( mTableView ) );
371  updateColumns();
372  // Make sure columns are updated when rows are added
373  connect( mModel, &QgsAggregateMappingModel::rowsInserted, this, [ = ] { updateColumns(); } );
374  connect( mModel, &QgsAggregateMappingModel::modelReset, this, [ = ] { updateColumns(); } );
375  connect( mModel, &QgsAggregateMappingModel::dataChanged, this, &QgsAggregateMappingWidget::changed );
376  connect( mModel, &QgsAggregateMappingModel::rowsInserted, this, &QgsAggregateMappingWidget::changed );
377  connect( mModel, &QgsAggregateMappingModel::rowsRemoved, this, &QgsAggregateMappingWidget::changed );
378  connect( mModel, &QgsAggregateMappingModel::modelReset, this, &QgsAggregateMappingWidget::changed );
379 }
380 
382 {
383  return qobject_cast<QgsAggregateMappingModel *>( mModel );
384 }
385 
386 QList<QgsAggregateMappingModel::Aggregate> QgsAggregateMappingWidget::mapping() const
387 {
388  return model()->mapping();
389 }
390 
391 void QgsAggregateMappingWidget::setMapping( const QList<QgsAggregateMappingModel::Aggregate> &mapping )
392 {
393  model()->setMapping( mapping );
394 }
395 
397 {
398  return mTableView->selectionModel();
399 }
400 
402 {
403  model()->setSourceFields( sourceFields );
404 }
405 
407 {
408  mSourceLayer = layer;
409 }
410 
412 {
413  return mSourceLayer;
414 }
415 
416 void QgsAggregateMappingWidget::scrollTo( const QModelIndex &index ) const
417 {
418  mTableView->scrollTo( index );
419 }
420 
422 {
424 }
425 
426 void QgsAggregateMappingWidget::appendField( const QgsField &field, const QString &source, const QString &aggregate )
427 {
428  model()->appendField( field, source, aggregate );
429 }
430 
432 {
433  if ( ! mTableView->selectionModel()->hasSelection() )
434  return false;
435 
436  std::list<int> rowsToRemove { selectedRows() };
437  rowsToRemove.reverse();
438  for ( const int row : rowsToRemove )
439  {
440  if ( ! model()->removeField( model()->index( row, 0, QModelIndex() ) ) )
441  {
442  return false;
443  }
444  }
445  return true;
446 }
447 
449 {
450  if ( ! mTableView->selectionModel()->hasSelection() )
451  return false;
452 
453  const std::list<int> rowsToMoveUp { selectedRows() };
454  for ( const int row : rowsToMoveUp )
455  {
456  if ( ! model()->moveUp( model()->index( row, 0, QModelIndex() ) ) )
457  {
458  return false;
459  }
460  }
461  return true;
462 }
463 
465 {
466  if ( ! mTableView->selectionModel()->hasSelection() )
467  return false;
468 
469  std::list<int> rowsToMoveDown { selectedRows() };
470  rowsToMoveDown.reverse();
471  for ( const int row : rowsToMoveDown )
472  {
473  if ( ! model()->moveDown( model()->index( row, 0, QModelIndex() ) ) )
474  {
475  return false;
476  }
477  }
478  return true;
479 }
480 
481 void QgsAggregateMappingWidget::updateColumns()
482 {
483  for ( int i = 0; i < mModel->rowCount(); ++i )
484  {
485  mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::SourceExpression ) ) );
486  mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::DestinationType ) ) );
487  mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::Aggregate ) ) );
488  }
489 
490  for ( int i = 0; i < mModel->columnCount(); ++i )
491  {
492  mTableView->resizeColumnToContents( i );
493  }
494 }
495 
496 std::list<int> QgsAggregateMappingWidget::selectedRows()
497 {
498  std::list<int> rows;
499  if ( mTableView->selectionModel()->hasSelection() )
500  {
501  const QModelIndexList constSelection { mTableView->selectionModel()->selectedIndexes() };
502  for ( const QModelIndex &index : constSelection )
503  {
504  rows.push_back( index.row() );
505  }
506  rows.sort();
507  rows.unique();
508  }
509  return rows;
510 }
511 
512 
513 
514 //
515 // AggregateDelegate
516 //
517 
518 QgsAggregateMappingWidget::AggregateDelegate::AggregateDelegate( QObject *parent )
519  : QStyledItemDelegate( parent )
520 {
521 }
522 
523 QWidget *QgsAggregateMappingWidget::AggregateDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex & ) const
524 {
525  Q_UNUSED( option )
526  QComboBox *editor = new QComboBox( parent );
527 
528  const QStringList aggregateList { QgsAggregateMappingWidget::AggregateDelegate::aggregates() };
529  int i = 0;
530  for ( const QString &aggregate : aggregateList )
531  {
532  editor->addItem( aggregate );
533  editor->setItemData( i, aggregate, Qt::UserRole );
534  ++i;
535  }
536 
537  connect( editor,
538  qOverload<int >( &QComboBox::currentIndexChanged ),
539  this,
540  [ = ]( int currentIndex )
541  {
542  Q_UNUSED( currentIndex )
543  const_cast< QgsAggregateMappingWidget::AggregateDelegate *>( this )->emit commitData( editor );
544  } );
545 
546  return editor;
547 }
548 
549 void QgsAggregateMappingWidget::AggregateDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
550 {
551  QComboBox *editorWidget { qobject_cast<QComboBox *>( editor ) };
552  if ( ! editorWidget )
553  return;
554 
555  const QVariant value = index.model()->data( index, Qt::EditRole );
556  editorWidget->setCurrentIndex( editorWidget->findData( value ) );
557 }
558 
559 void QgsAggregateMappingWidget::AggregateDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
560 {
561  QComboBox *editorWidget { qobject_cast<QComboBox *>( editor ) };
562  if ( ! editorWidget )
563  return;
564 
565  const QVariant currentValue = editorWidget->currentData( );
566  model->setData( index, currentValue, Qt::EditRole );
567 }
568 
569 const QStringList QgsAggregateMappingWidget::AggregateDelegate::aggregates()
570 {
571  static QStringList sAggregates;
572  static std::once_flag initialized;
573  std::call_once( initialized, [ = ]( )
574  {
575  sAggregates << QStringLiteral( "first_value" )
576  << QStringLiteral( "last_value" );
577 
578  const QList<QgsExpressionFunction *> functions = QgsExpression::Functions();
579  for ( const QgsExpressionFunction *function : functions )
580  {
581  if ( !function || function->isDeprecated() || function->name().isEmpty() || function->name().at( 0 ) == '_' )
582  continue;
583 
584  if ( function->groups().contains( QLatin1String( "Aggregates" ) ) )
585  {
586  if ( function->name() == QLatin1String( "aggregate" )
587  || function->name() == QLatin1String( "relation_aggregate" ) )
588  continue;
589 
590  sAggregates.append( function->name() );
591  }
592 
593  std::sort( sAggregates.begin(), sAggregates.end() );
594  }
595  } );
596 
597  return sAggregates;
598 }
599 
The QgsAggregateMappingModel holds mapping information for defining sets of aggregates of fields from...
QgsFields sourceFields() const
Returns a list of source fields.
void appendField(const QgsField &field, const QString &source=QString(), const QString &aggregate=QString())
Appends a new field to the model, with an optional source and aggregate.
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
int columnCount(const QModelIndex &parent=QModelIndex()) const override
ColumnDataIndex
The ColumnDataIndex enum represents the column index for the view.
@ DestinationPrecision
Destination field precision.
@ DestinationName
Destination field name.
@ DestinationType
Destination field type string.
@ DestinationLength
Destination field length.
Qt::ItemFlags flags(const QModelIndex &index) const override
bool removeField(const QModelIndex &index)
Removes the field at index from the model, returns true on success.
bool moveUp(const QModelIndex &index)
Moves down the field at index.
QVariant data(const QModelIndex &index, int role) const override
QList< QgsAggregateMappingModel::Aggregate > mapping() const
Returns a list of Aggregate objects representing the current status of the model.
bool setData(const QModelIndex &index, const QVariant &value, int role) override
QgsExpressionContextGenerator * contextGenerator() const
Returns the context generator with the source fields.
bool moveDown(const QModelIndex &index)
Moves up the field at index.
void setSourceFields(const QgsFields &sourceFields)
Set source fields to sourceFields.
void setMapping(const QList< QgsAggregateMappingModel::Aggregate > &mapping)
Sets the mapping to show in the model.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
void setBaseExpressionContextGenerator(const QgsExpressionContextGenerator *generator)
Sets the base expression context generator, which will generate the expression contexts for expressio...
QgsAggregateMappingModel(const QgsFields &sourceFields=QgsFields(), QObject *parent=nullptr)
Constructs a QgsAggregateMappingModel from a set of sourceFields.
QList< QgsAggregateMappingModel::Aggregate > mapping() const
Returns a list of Aggregate objects representing the current status of the underlying mapping model.
void changed()
Emitted when the aggregates defined in the widget are changed.
void setMapping(const QList< QgsAggregateMappingModel::Aggregate > &mapping)
Sets the mapping to show in the model.
void appendField(const QgsField &field, const QString &source=QString(), const QString &aggregate=QString())
Appends a new field to the model, with an optional source and aggregate.
QgsVectorLayer * sourceLayer()
Returns the source layer for use when generating expression previews.
QgsAggregateMappingModel * model() const
Returns the underlying mapping model.
void setSourceFields(const QgsFields &sourceFields)
Set source fields of the underlying mapping model to sourceFields.
bool moveSelectedFieldsDown()
Moves down currently selected field.
QgsAggregateMappingWidget(QWidget *parent=nullptr, const QgsFields &sourceFields=QgsFields())
Constructs a QgsAggregateMappingWidget from a set of sourceFields.
void registerExpressionContextGenerator(const QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
QItemSelectionModel * selectionModel()
Returns the selection model.
bool moveSelectedFieldsUp()
Moves up currently selected field.
void scrollTo(const QModelIndex &index) const
Scroll the fields view to index.
bool removeSelectedFields()
Removes the currently selected field from the model.
void setSourceLayer(QgsVectorLayer *layer)
Sets a source layer to use when generating expression previews in the widget.
Abstract interface for generating an expression context.
A abstract base class for defining QgsExpression functions.
Class for parsing and evaluation of expressions (formerly called "search strings").
static const QList< QgsExpressionFunction * > & Functions()
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
The QgsFieldMappingModel holds mapping information for mapping from one set of QgsFields to another,...
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
void setPrecision(int precision)
Set the field precision.
Definition: qgsfield.cpp:199
void setName(const QString &name)
Set the field name.
Definition: qgsfield.cpp:175
void setLength(int len)
Set the field length.
Definition: qgsfield.cpp:195
void setTypeName(const QString &typeName)
Set the field type.
Definition: qgsfield.cpp:190
Container of fields for a vector layer.
Definition: qgsfields.h:45
Base class for any widget that can be shown as a inline panel.
Represents a vector layer which manages a vector based data sets.
const QgsField & field
Definition: qgsfield.h:463
int precision
The Aggregate struct holds information about an aggregate column.
QString source
The source expression used as the input for the aggregate calculation.
QgsField field
The field in its current status (it might have been renamed)