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