QGIS API Documentation 3.27.0-Master (9c08adf5ef)
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
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//
32// QgsAggregateMappingModel
33//
34
36 QObject *parent )
37 : QAbstractTableModel( parent )
38 , mExpressionContextGenerator( new QgsFieldMappingModel::ExpressionContextGenerator( sourceFields ) )
39{
41}
42
43QVariant QgsAggregateMappingModel::headerData( int section, Qt::Orientation orientation, int role ) const
44{
45 if ( role == Qt::DisplayRole )
46 {
47 switch ( orientation )
48 {
49 case Qt::Horizontal:
50 {
51 switch ( static_cast<ColumnDataIndex>( section ) )
52 {
54 {
55 return tr( "Source Expression" );
56 }
58 {
59 return tr( "Aggregate Function" );
60 }
62 {
63 return tr( "Delimiter" );
64 }
66 {
67 return tr( "Name" );
68 }
70 {
71 return tr( "Type" );
72 }
74 {
75 return tr( "Length" );
76 }
78 {
79 return tr( "Precision" );
80 }
81 }
82 break;
83 }
84 case Qt::Vertical:
85 {
86 return section;
87 }
88 }
89 }
90 return QVariant();
91}
92
94{
95 return mSourceFields;
96}
97
98int QgsAggregateMappingModel::rowCount( const QModelIndex &parent ) const
99{
100 if ( parent.isValid() )
101 return 0;
102 return mMapping.count();
103}
104
105int QgsAggregateMappingModel::columnCount( const QModelIndex &parent ) const
106{
107 if ( parent.isValid() )
108 return 0;
109 return 7;
110}
111
112QVariant QgsAggregateMappingModel::data( const QModelIndex &index, int role ) const
113{
114 if ( index.isValid() )
115 {
116 const ColumnDataIndex col { static_cast<ColumnDataIndex>( index.column() ) };
117 const Aggregate &agg { mMapping.at( index.row() ) };
118
119 switch ( role )
120 {
121 case Qt::DisplayRole:
122 case Qt::EditRole:
123 {
124 switch ( col )
125 {
127 {
128 return agg.source;
129 }
131 {
132 return agg.aggregate;
133 }
135 {
136 return agg.delimiter;
137 }
139 {
140 return agg.field.displayName();
141 }
143 {
144 return agg.field.typeName();
145 }
147 {
148 return agg.field.length();
149 }
151 {
152 return agg.field.precision();
153 }
154 }
155 break;
156 }
157 }
158 }
159 return QVariant();
160}
161
162Qt::ItemFlags QgsAggregateMappingModel::flags( const QModelIndex &index ) const
163{
164 if ( index.isValid() )
165 {
166 return Qt::ItemFlags( Qt::ItemIsSelectable |
167 Qt::ItemIsEditable |
168 Qt::ItemIsEnabled );
169 }
170 return Qt::ItemFlags();
171}
172
173bool QgsAggregateMappingModel::setData( const QModelIndex &index, const QVariant &value, int role )
174{
175 if ( index.isValid() )
176 {
177 if ( role == Qt::EditRole )
178 {
179 Aggregate &f = mMapping[index.row()];
180 switch ( static_cast<ColumnDataIndex>( index.column() ) )
181 {
183 {
184 const QgsExpression exp { value.toString() };
185 f.source = exp;
186 break;
187 }
189 {
190 f.aggregate = value.toString();
191 break;
192 }
194 {
195 f.delimiter = value.toString();
196 break;
197 }
199 {
200 f.field.setName( value.toString() );
201 break;
202 }
204 {
205 QgsFieldMappingModel::setFieldTypeFromName( f.field, value.toString() );
206 break;
207 }
209 {
210 bool ok;
211 const int length { value.toInt( &ok ) };
212 if ( ok )
213 f.field.setLength( length );
214 break;
215 }
217 {
218 bool ok;
219 const int precision { value.toInt( &ok ) };
220 if ( ok )
222 break;
223 }
224 }
225 emit dataChanged( index, index );
226 }
227 return true;
228 }
229 else
230 {
231 return false;
232 }
233}
234
235
236bool QgsAggregateMappingModel::moveUpOrDown( const QModelIndex &index, bool up )
237{
238 if ( ! index.isValid() && index.model() == this )
239 return false;
240
241 // Always swap down
242 const int row { up ? index.row() - 1 : index.row() };
243 // Range checking
244 if ( row < 0 || row + 1 >= rowCount( QModelIndex() ) )
245 {
246 return false;
247 }
248 beginMoveRows( QModelIndex( ), row, row, QModelIndex(), row + 2 );
249#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
250 mMapping.swap( row, row + 1 );
251#else
252 mMapping.swapItemsAt( row, row + 1 );
253#endif
254 endMoveRows();
255 return true;
256}
257
259{
260 mSourceFields = sourceFields;
261 if ( mExpressionContextGenerator )
262 mExpressionContextGenerator->setSourceFields( mSourceFields );
263
264 const QStringList usedFields;
265 beginResetModel();
266 mMapping.clear();
267
268 for ( const QgsField &f : sourceFields )
269 {
270 Aggregate aggregate;
271 aggregate.field = f;
272 aggregate.field.setTypeName( QgsFieldMappingModel::qgsFieldToTypeName( f ) );
273 aggregate.source = QgsExpression::quotedColumnRef( f.name() );
274
275 if ( f.isNumeric() )
276 aggregate.aggregate = QStringLiteral( "sum" );
277 else if ( f.type() == QVariant::String || ( f.type() == QVariant::List && f.subType() == QVariant::String ) )
278 aggregate.aggregate = QStringLiteral( "concatenate" );
279
280 aggregate.delimiter = ',';
281
282 mMapping.push_back( aggregate );
283 }
284 endResetModel();
285}
286
288{
289 return mExpressionContextGenerator.get();
290}
291
293{
294 mExpressionContextGenerator->setBaseExpressionContextGenerator( generator );
295}
296
297QList<QgsAggregateMappingModel::Aggregate> QgsAggregateMappingModel::mapping() const
298{
299 return mMapping;
300}
301
302void QgsAggregateMappingModel::setMapping( const QList<QgsAggregateMappingModel::Aggregate> &mapping )
303{
304 beginResetModel();
305 mMapping = mapping;
306 for ( auto &agg : mMapping )
307 {
308 agg.field.setTypeName( QgsFieldMappingModel::qgsFieldToTypeName( agg.field ) );
309 }
310 endResetModel();
311}
312
313void QgsAggregateMappingModel::appendField( const QgsField &field, const QString &source, const QString &aggregate )
314{
315 const int lastRow { rowCount( QModelIndex( ) ) };
316 beginInsertRows( QModelIndex(), lastRow, lastRow );
317 Aggregate agg;
318 agg.field = field;
319 agg.field.setTypeName( QgsFieldMappingModel::qgsFieldToTypeName( field ) );
320 agg.source = source;
321 agg.aggregate = aggregate;
322 agg.delimiter = ',';
323 mMapping.push_back( agg );
324 endInsertRows( );
325}
326
327bool QgsAggregateMappingModel::removeField( const QModelIndex &index )
328{
329 if ( index.isValid() && index.model() == this && index.row() < rowCount( QModelIndex() ) )
330 {
331 beginRemoveRows( QModelIndex(), index.row(), index.row() );
332 mMapping.removeAt( index.row() );
333 endRemoveRows();
334 return true;
335 }
336 else
337 {
338 return false;
339 }
340}
341
342bool QgsAggregateMappingModel::moveUp( const QModelIndex &index )
343{
344 return moveUpOrDown( index );
345}
346
347bool QgsAggregateMappingModel::moveDown( const QModelIndex &index )
348{
349 return moveUpOrDown( index, false );
350}
351
352
353//
354// QgsAggregateMappingWidget
355//
356
358 const QgsFields &sourceFields )
359 : QgsPanelWidget( parent )
360{
361 QVBoxLayout *verticalLayout = new QVBoxLayout();
362 verticalLayout->setContentsMargins( 0, 0, 0, 0 );
363 mTableView = new QTableView();
364 verticalLayout->addWidget( mTableView );
365 setLayout( verticalLayout );
366
367 mModel = new QgsAggregateMappingModel( sourceFields, this );
368 mTableView->setModel( mModel );
369 mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::SourceExpression ), new QgsFieldMappingExpressionDelegate( this ) );
370 mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::Aggregate ), new QgsAggregateMappingDelegate( mTableView ) );
371 mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::DestinationType ), new QgsFieldMappingTypeDelegate( mTableView ) );
372 updateColumns();
373 // Make sure columns are updated when rows are added
374 connect( mModel, &QgsAggregateMappingModel::rowsInserted, this, [ = ] { updateColumns(); } );
375 connect( mModel, &QgsAggregateMappingModel::modelReset, this, [ = ] { updateColumns(); } );
376 connect( mModel, &QgsAggregateMappingModel::dataChanged, this, &QgsAggregateMappingWidget::changed );
377 connect( mModel, &QgsAggregateMappingModel::rowsInserted, this, &QgsAggregateMappingWidget::changed );
378 connect( mModel, &QgsAggregateMappingModel::rowsRemoved, this, &QgsAggregateMappingWidget::changed );
379 connect( mModel, &QgsAggregateMappingModel::modelReset, this, &QgsAggregateMappingWidget::changed );
380}
381
383{
384 return qobject_cast<QgsAggregateMappingModel *>( mModel );
385}
386
387QList<QgsAggregateMappingModel::Aggregate> QgsAggregateMappingWidget::mapping() const
388{
389 return model()->mapping();
390}
391
392void QgsAggregateMappingWidget::setMapping( const QList<QgsAggregateMappingModel::Aggregate> &mapping )
393{
395}
396
398{
399 return mTableView->selectionModel();
400}
401
403{
404 model()->setSourceFields( sourceFields );
405}
406
408{
409 mSourceLayer = layer;
410}
411
413{
414 return mSourceLayer;
415}
416
417void QgsAggregateMappingWidget::scrollTo( const QModelIndex &index ) const
418{
419 mTableView->scrollTo( index );
420}
421
423{
425}
426
427void QgsAggregateMappingWidget::appendField( const QgsField &field, const QString &source, const QString &aggregate )
428{
429 model()->appendField( field, source, aggregate );
430}
431
433{
434 if ( ! mTableView->selectionModel()->hasSelection() )
435 return false;
436
437 std::list<int> rowsToRemove { selectedRows() };
438 rowsToRemove.reverse();
439 for ( const int row : rowsToRemove )
440 {
441 if ( ! model()->removeField( model()->index( row, 0, QModelIndex() ) ) )
442 {
443 return false;
444 }
445 }
446 return true;
447}
448
450{
451 if ( ! mTableView->selectionModel()->hasSelection() )
452 return false;
453
454 const std::list<int> rowsToMoveUp { selectedRows() };
455 for ( const int row : rowsToMoveUp )
456 {
457 if ( ! model()->moveUp( model()->index( row, 0, QModelIndex() ) ) )
458 {
459 return false;
460 }
461 }
462 return true;
463}
464
466{
467 if ( ! mTableView->selectionModel()->hasSelection() )
468 return false;
469
470 std::list<int> rowsToMoveDown { selectedRows() };
471 rowsToMoveDown.reverse();
472 for ( const int row : rowsToMoveDown )
473 {
474 if ( ! model()->moveDown( model()->index( row, 0, QModelIndex() ) ) )
475 {
476 return false;
477 }
478 }
479 return true;
480}
481
482void QgsAggregateMappingWidget::updateColumns()
483{
484 for ( int i = 0; i < mModel->rowCount(); ++i )
485 {
486 mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::SourceExpression ) ) );
487 mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::DestinationType ) ) );
488 mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::Aggregate ) ) );
489 }
490
491 for ( int i = 0; i < mModel->columnCount(); ++i )
492 {
493 mTableView->resizeColumnToContents( i );
494 }
495}
496
497std::list<int> QgsAggregateMappingWidget::selectedRows()
498{
499 std::list<int> rows;
500 if ( mTableView->selectionModel()->hasSelection() )
501 {
502 const QModelIndexList constSelection { mTableView->selectionModel()->selectedIndexes() };
503 for ( const QModelIndex &index : constSelection )
504 {
505 rows.push_back( index.row() );
506 }
507 rows.sort();
508 rows.unique();
509 }
510 return rows;
511}
512
513
515
516//
517// AggregateDelegate
518//
519
520QgsAggregateMappingDelegate::QgsAggregateMappingDelegate( QObject *parent )
521 : QStyledItemDelegate( parent )
522{
523}
524
525QWidget *QgsAggregateMappingDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex & ) const
526{
527 Q_UNUSED( option )
528 QComboBox *editor = new QComboBox( parent );
529
530 const QStringList aggregateList { aggregates() };
531 int i = 0;
532 for ( const QString &aggregate : aggregateList )
533 {
534 editor->addItem( aggregate );
535 editor->setItemData( i, aggregate, Qt::UserRole );
536 ++i;
537 }
538
539 connect( editor,
540 qOverload<int >( &QComboBox::currentIndexChanged ),
541 this,
542 [ = ]( int currentIndex )
543 {
544 Q_UNUSED( currentIndex )
545 const_cast< QgsAggregateMappingDelegate *>( this )->emit commitData( editor );
546 } );
547
548 return editor;
549}
550
551void QgsAggregateMappingDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
552{
553 QComboBox *editorWidget { qobject_cast<QComboBox *>( editor ) };
554 if ( ! editorWidget )
555 return;
556
557 const QVariant value = index.model()->data( index, Qt::EditRole );
558 editorWidget->setCurrentIndex( editorWidget->findData( value ) );
559}
560
561void QgsAggregateMappingDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
562{
563 QComboBox *editorWidget { qobject_cast<QComboBox *>( editor ) };
564 if ( ! editorWidget )
565 return;
566
567 const QVariant currentValue = editorWidget->currentData( );
568 model->setData( index, currentValue, Qt::EditRole );
569}
570
571const QStringList QgsAggregateMappingDelegate::aggregates()
572{
573 static QStringList sAggregates;
574 static std::once_flag initialized;
575 std::call_once( initialized, [ = ]( )
576 {
577 sAggregates << QStringLiteral( "first_value" )
578 << QStringLiteral( "last_value" );
579
580 const QList<QgsExpressionFunction *> functions = QgsExpression::Functions();
581 for ( const QgsExpressionFunction *function : functions )
582 {
583 if ( !function || function->isDeprecated() || function->name().isEmpty() || function->name().at( 0 ) == '_' )
584 continue;
585
586 if ( function->groups().contains( QLatin1String( "Aggregates" ) ) )
587 {
588 if ( function->name() == QLatin1String( "aggregate" )
589 || function->name() == QLatin1String( "relation_aggregate" ) )
590 continue;
591
592 sAggregates.append( function->name() );
593 }
594
595 std::sort( sAggregates.begin(), sAggregates.end() );
596 }
597 } );
598
599 return sAggregates;
600}
601
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)