QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 static_cast<int>( agg.field.type() );
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  f.field.setType( static_cast<QVariant::Type>( value.toInt( ) ) );
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.source = QgsExpression::quotedColumnRef( f.name() );
272 
273  if ( f.isNumeric() )
274  aggregate.aggregate = QStringLiteral( "sum" );
275  else if ( f.type() == QVariant::String || ( f.type() == QVariant::List && f.subType() == QVariant::String ) )
276  aggregate.aggregate = QStringLiteral( "concatenate" );
277 
278  aggregate.delimiter = ',';
279 
280  mMapping.push_back( aggregate );
281  }
282  endResetModel();
283 }
284 
286 {
287  return mExpressionContextGenerator.get();
288 }
289 
291 {
292  mExpressionContextGenerator->setBaseExpressionContextGenerator( generator );
293 }
294 
295 QList<QgsAggregateMappingModel::Aggregate> QgsAggregateMappingModel::mapping() const
296 {
297  return mMapping;
298 }
299 
300 void QgsAggregateMappingModel::setMapping( const QList<QgsAggregateMappingModel::Aggregate> &mapping )
301 {
302  beginResetModel();
303  mMapping = mapping;
304  endResetModel();
305 }
306 
307 void QgsAggregateMappingModel::appendField( const QgsField &field, const QString &source, const QString &aggregate )
308 {
309  const int lastRow { rowCount( QModelIndex( ) ) };
310  beginInsertRows( QModelIndex(), lastRow, lastRow );
311  Aggregate agg;
312  agg.field = field;
313  agg.source = source;
314  agg.aggregate = aggregate;
315  agg.delimiter = ',';
316  mMapping.push_back( agg );
317  endInsertRows( );
318 }
319 
320 bool QgsAggregateMappingModel::removeField( const QModelIndex &index )
321 {
322  if ( index.isValid() && index.model() == this && index.row() < rowCount( QModelIndex() ) )
323  {
324  beginRemoveRows( QModelIndex(), index.row(), index.row() );
325  mMapping.removeAt( index.row() );
326  endRemoveRows();
327  return true;
328  }
329  else
330  {
331  return false;
332  }
333 }
334 
335 bool QgsAggregateMappingModel::moveUp( const QModelIndex &index )
336 {
337  return moveUpOrDown( index );
338 }
339 
340 bool QgsAggregateMappingModel::moveDown( const QModelIndex &index )
341 {
342  return moveUpOrDown( index, false );
343 }
344 
345 
346 //
347 // QgsAggregateMappingWidget
348 //
349 
351  const QgsFields &sourceFields )
352  : QgsPanelWidget( parent )
353 {
354  QVBoxLayout *verticalLayout = new QVBoxLayout();
355  verticalLayout->setContentsMargins( 0, 0, 0, 0 );
356  mTableView = new QTableView();
357  verticalLayout->addWidget( mTableView );
358  setLayout( verticalLayout );
359 
360  mModel = new QgsAggregateMappingModel( sourceFields, this );
361  mTableView->setModel( mModel );
362  mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::SourceExpression ), new QgsFieldMappingWidget::ExpressionDelegate( this ) );
363  mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::Aggregate ), new QgsAggregateMappingWidget::AggregateDelegate( mTableView ) );
364  mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::DestinationType ), new QgsFieldMappingWidget::TypeDelegate( mTableView ) );
365  updateColumns();
366  // Make sure columns are updated when rows are added
367  connect( mModel, &QgsAggregateMappingModel::rowsInserted, this, [ = ] { updateColumns(); } );
368  connect( mModel, &QgsAggregateMappingModel::modelReset, this, [ = ] { updateColumns(); } );
369  connect( mModel, &QgsAggregateMappingModel::dataChanged, this, &QgsAggregateMappingWidget::changed );
370  connect( mModel, &QgsAggregateMappingModel::rowsInserted, this, &QgsAggregateMappingWidget::changed );
371  connect( mModel, &QgsAggregateMappingModel::rowsRemoved, this, &QgsAggregateMappingWidget::changed );
372  connect( mModel, &QgsAggregateMappingModel::modelReset, this, &QgsAggregateMappingWidget::changed );
373 }
374 
376 {
377  return qobject_cast<QgsAggregateMappingModel *>( mModel );
378 }
379 
380 QList<QgsAggregateMappingModel::Aggregate> QgsAggregateMappingWidget::mapping() const
381 {
382  return model()->mapping();
383 }
384 
385 void QgsAggregateMappingWidget::setMapping( const QList<QgsAggregateMappingModel::Aggregate> &mapping )
386 {
387  model()->setMapping( mapping );
388 }
389 
391 {
392  return mTableView->selectionModel();
393 }
394 
396 {
397  model()->setSourceFields( sourceFields );
398 }
399 
401 {
402  mSourceLayer = layer;
403 }
404 
406 {
407  return mSourceLayer;
408 }
409 
410 void QgsAggregateMappingWidget::scrollTo( const QModelIndex &index ) const
411 {
412  mTableView->scrollTo( index );
413 }
414 
416 {
418 }
419 
420 void QgsAggregateMappingWidget::appendField( const QgsField &field, const QString &source, const QString &aggregate )
421 {
422  model()->appendField( field, source, aggregate );
423 }
424 
426 {
427  if ( ! mTableView->selectionModel()->hasSelection() )
428  return false;
429 
430  std::list<int> rowsToRemove { selectedRows() };
431  rowsToRemove.reverse();
432  for ( const int row : rowsToRemove )
433  {
434  if ( ! model()->removeField( model()->index( row, 0, QModelIndex() ) ) )
435  {
436  return false;
437  }
438  }
439  return true;
440 }
441 
443 {
444  if ( ! mTableView->selectionModel()->hasSelection() )
445  return false;
446 
447  const std::list<int> rowsToMoveUp { selectedRows() };
448  for ( const int row : rowsToMoveUp )
449  {
450  if ( ! model()->moveUp( model()->index( row, 0, QModelIndex() ) ) )
451  {
452  return false;
453  }
454  }
455  return true;
456 }
457 
459 {
460  if ( ! mTableView->selectionModel()->hasSelection() )
461  return false;
462 
463  std::list<int> rowsToMoveDown { selectedRows() };
464  rowsToMoveDown.reverse();
465  for ( const int row : rowsToMoveDown )
466  {
467  if ( ! model()->moveDown( model()->index( row, 0, QModelIndex() ) ) )
468  {
469  return false;
470  }
471  }
472  return true;
473 }
474 
475 void QgsAggregateMappingWidget::updateColumns()
476 {
477  for ( int i = 0; i < mModel->rowCount(); ++i )
478  {
479  mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::SourceExpression ) ) );
480  mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::DestinationType ) ) );
481  mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::Aggregate ) ) );
482  }
483 
484  for ( int i = 0; i < mModel->columnCount(); ++i )
485  {
486  mTableView->resizeColumnToContents( i );
487  }
488 }
489 
490 std::list<int> QgsAggregateMappingWidget::selectedRows()
491 {
492  std::list<int> rows;
493  if ( mTableView->selectionModel()->hasSelection() )
494  {
495  const QModelIndexList constSelection { mTableView->selectionModel()->selectedIndexes() };
496  for ( const QModelIndex &index : constSelection )
497  {
498  rows.push_back( index.row() );
499  }
500  rows.sort();
501  rows.unique();
502  }
503  return rows;
504 }
505 
506 
507 
508 //
509 // AggregateDelegate
510 //
511 
512 QgsAggregateMappingWidget::AggregateDelegate::AggregateDelegate( QObject *parent )
513  : QStyledItemDelegate( parent )
514 {
515 }
516 
517 QWidget *QgsAggregateMappingWidget::AggregateDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex & ) const
518 {
519  Q_UNUSED( option )
520  QComboBox *editor = new QComboBox( parent );
521 
522  const QStringList aggregateList { QgsAggregateMappingWidget::AggregateDelegate::aggregates() };
523  int i = 0;
524  for ( const QString &aggregate : aggregateList )
525  {
526  editor->addItem( aggregate );
527  editor->setItemData( i, aggregate, Qt::UserRole );
528  ++i;
529  }
530 
531  connect( editor,
532  qOverload<int >( &QComboBox::currentIndexChanged ),
533  this,
534  [ = ]( int currentIndex )
535  {
536  Q_UNUSED( currentIndex )
537  const_cast< QgsAggregateMappingWidget::AggregateDelegate *>( this )->emit commitData( editor );
538  } );
539 
540  return editor;
541 }
542 
543 void QgsAggregateMappingWidget::AggregateDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
544 {
545  QComboBox *editorWidget { qobject_cast<QComboBox *>( editor ) };
546  if ( ! editorWidget )
547  return;
548 
549  const QVariant value = index.model()->data( index, Qt::EditRole );
550  editorWidget->setCurrentIndex( editorWidget->findData( value ) );
551 }
552 
553 void QgsAggregateMappingWidget::AggregateDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
554 {
555  QComboBox *editorWidget { qobject_cast<QComboBox *>( editor ) };
556  if ( ! editorWidget )
557  return;
558 
559  const QVariant currentValue = editorWidget->currentData( );
560  model->setData( index, currentValue, Qt::EditRole );
561 }
562 
563 const QStringList QgsAggregateMappingWidget::AggregateDelegate::aggregates()
564 {
565  static QStringList sAggregates;
566  static std::once_flag initialized;
567  std::call_once( initialized, [ = ]( )
568  {
569  sAggregates << QStringLiteral( "first_value" )
570  << QStringLiteral( "last_value" );
571 
572  const QList<QgsExpressionFunction *> functions = QgsExpression::Functions();
573  for ( const QgsExpressionFunction *function : functions )
574  {
575  if ( !function || function->isDeprecated() || function->name().isEmpty() || function->name().at( 0 ) == '_' )
576  continue;
577 
578  if ( function->groups().contains( QLatin1String( "Aggregates" ) ) )
579  {
580  if ( function->name() == QLatin1String( "aggregate" )
581  || function->name() == QLatin1String( "relation_aggregate" ) )
582  continue;
583 
584  sAggregates.append( function->name() );
585  }
586 
587  std::sort( sAggregates.begin(), sAggregates.end() );
588  }
589  } );
590 
591  return sAggregates;
592 }
593 
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 QVariant::Type casted to (int)
@ 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:198
void setName(const QString &name)
Set the field name.
Definition: qgsfield.cpp:174
void setLength(int len)
Set the field length.
Definition: qgsfield.cpp:194
void setType(QVariant::Type type)
Set variant type.
Definition: qgsfield.cpp:179
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)