QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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
17
18#include <mutex>
19
23
24#include <QBoxLayout>
25#include <QLineEdit>
26#include <QMessageBox>
27#include <QPushButton>
28#include <QStandardItemModel>
29#include <QTableView>
30#include <QToolButton>
31
32#include "moc_qgsprocessingaggregatewidgets.cpp"
33
34//
35// QgsAggregateMappingModel
36//
37
39 : QAbstractTableModel( parent )
40 , mExpressionContextGenerator( new QgsFieldMappingModel::ExpressionContextGenerator( sourceFields ) )
41{
43}
44
45QVariant QgsAggregateMappingModel::headerData( int section, Qt::Orientation orientation, int role ) const
46{
47 if ( role == Qt::DisplayRole )
48 {
49 switch ( orientation )
50 {
51 case Qt::Horizontal:
52 {
53 switch ( static_cast<ColumnDataIndex>( section ) )
54 {
56 {
57 return tr( "Source Expression" );
58 }
60 {
61 return tr( "Aggregate Function" );
62 }
64 {
65 return tr( "Delimiter" );
66 }
68 {
69 return tr( "Name" );
70 }
72 {
73 return tr( "Type" );
74 }
76 {
77 return tr( "Length" );
78 }
80 {
81 return tr( "Precision" );
82 }
83 }
84 break;
85 }
86 case Qt::Vertical:
87 {
88 return section;
89 }
90 }
91 }
92 return QVariant();
93}
94
96{
97 return mSourceFields;
98}
99
100int QgsAggregateMappingModel::rowCount( const QModelIndex &parent ) const
101{
102 if ( parent.isValid() )
103 return 0;
104 return mMapping.count();
105}
106
107int QgsAggregateMappingModel::columnCount( const QModelIndex &parent ) const
108{
109 if ( parent.isValid() )
110 return 0;
111 return 7;
112}
113
114QVariant QgsAggregateMappingModel::data( const QModelIndex &index, int role ) const
115{
116 if ( index.isValid() )
117 {
118 const ColumnDataIndex col { static_cast<ColumnDataIndex>( index.column() ) };
119 const Aggregate &agg { mMapping.at( index.row() ) };
120
121 switch ( role )
122 {
123 case Qt::DisplayRole:
124 case Qt::EditRole:
125 {
126 switch ( col )
127 {
129 {
130 return agg.source;
131 }
133 {
134 return agg.aggregate;
135 }
137 {
138 return agg.delimiter;
139 }
141 {
142 return agg.field.displayName();
143 }
145 {
146 return agg.field.typeName();
147 }
149 {
150 return agg.field.length();
151 }
153 {
154 return agg.field.precision();
155 }
156 }
157 break;
158 }
159 }
160 }
161 return QVariant();
162}
163
164Qt::ItemFlags QgsAggregateMappingModel::flags( const QModelIndex &index ) const
165{
166 if ( index.isValid() )
167 {
168 return Qt::ItemFlags( Qt::ItemIsSelectable | Qt::ItemIsEditable | 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 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 )
221 f.field.setPrecision( precision );
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
254QString QgsAggregateMappingModel::qgsFieldToTypeName( const QgsField &field )
255{
256 const QList<QgsVectorDataProvider::NativeType> types = QgsFieldMappingModel::supportedDataTypes();
257 for ( const QgsVectorDataProvider::NativeType &type : types )
258 {
259 if ( type.mType == field.type() && type.mSubType == field.subType() )
260 {
261 return type.mTypeName;
262 }
263 }
264 return QString();
265}
266
267void QgsAggregateMappingModel::setFieldTypeFromName( QgsField &field, const QString &name )
268{
269 const QList<QgsVectorDataProvider::NativeType> types = QgsFieldMappingModel::supportedDataTypes();
270 for ( const QgsVectorDataProvider::NativeType &type : types )
271 {
272 if ( type.mTypeName == name )
273 {
274 field.setType( type.mType );
275 field.setTypeName( type.mTypeName );
276 field.setSubType( type.mSubType );
277 return;
278 }
279 }
280}
281
283{
284 mSourceFields = sourceFields;
285 if ( mExpressionContextGenerator )
286 mExpressionContextGenerator->setSourceFields( mSourceFields );
287
288 beginResetModel();
289 mMapping.clear();
290
291 for ( const QgsField &f : sourceFields )
292 {
293 Aggregate aggregate;
294 aggregate.field = f;
295 aggregate.field.setTypeName( qgsFieldToTypeName( f ) );
296 aggregate.source = QgsExpression::quotedColumnRef( f.name() );
297
298 if ( f.isNumeric() )
299 aggregate.aggregate = QStringLiteral( "sum" );
300 else if ( f.type() == QMetaType::Type::QString || ( f.type() == QMetaType::Type::QVariantList && f.subType() == QMetaType::Type::QString ) )
301 aggregate.aggregate = QStringLiteral( "concatenate" );
302
303 aggregate.delimiter = ',';
304
305 mMapping.push_back( aggregate );
306 }
307 endResetModel();
308}
309
311{
312 return mExpressionContextGenerator.get();
313}
314
316{
317 mExpressionContextGenerator->setBaseExpressionContextGenerator( generator );
318}
319
320QList<QgsAggregateMappingModel::Aggregate> QgsAggregateMappingModel::mapping() const
321{
322 return mMapping;
323}
324
325void QgsAggregateMappingModel::setMapping( const QList<QgsAggregateMappingModel::Aggregate> &mapping )
326{
327 beginResetModel();
328 mMapping = mapping;
329 for ( auto &agg : mMapping )
330 {
331 agg.field.setTypeName( qgsFieldToTypeName( agg.field ) );
332 }
333 endResetModel();
334}
335
336void QgsAggregateMappingModel::appendField( const QgsField &field, const QString &source, const QString &aggregate )
337{
338 const int lastRow { rowCount( QModelIndex() ) };
339 beginInsertRows( QModelIndex(), lastRow, lastRow );
340 Aggregate agg;
341 agg.field = field;
342 agg.field.setTypeName( qgsFieldToTypeName( field ) );
343 agg.source = source;
344 agg.aggregate = aggregate;
345 agg.delimiter = ',';
346 mMapping.push_back( agg );
347 endInsertRows();
348}
349
350bool QgsAggregateMappingModel::removeField( const QModelIndex &index )
351{
352 if ( index.isValid() && index.model() == this && index.row() < rowCount( QModelIndex() ) )
353 {
354 beginRemoveRows( QModelIndex(), index.row(), index.row() );
355 mMapping.removeAt( index.row() );
356 endRemoveRows();
357 return true;
358 }
359 else
360 {
361 return false;
362 }
363}
364
365bool QgsAggregateMappingModel::moveUp( const QModelIndex &index )
366{
367 return moveUpOrDown( index );
368}
369
370bool QgsAggregateMappingModel::moveDown( const QModelIndex &index )
371{
372 return moveUpOrDown( index, false );
373}
374
375
376//
377// QgsAggregateMappingWidget
378//
379
381 : QgsPanelWidget( parent )
382{
383 QVBoxLayout *verticalLayout = new QVBoxLayout();
384 verticalLayout->setContentsMargins( 0, 0, 0, 0 );
385 mTableView = new QTableView();
386 verticalLayout->addWidget( mTableView );
387 setLayout( verticalLayout );
388
389 mModel = new QgsAggregateMappingModel( sourceFields, this );
390 mTableView->setModel( mModel );
391 mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::SourceExpression ), new QgsFieldMappingExpressionDelegate( this ) );
392 mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::Aggregate ), new QgsAggregateMappingDelegate( mTableView ) );
393 mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::DestinationType ), new QgsFieldMappingTypeDelegate( {}, mTableView ) );
394 updateColumns();
395 // Make sure columns are updated when rows are added
396 connect( mModel, &QgsAggregateMappingModel::rowsInserted, this, [this] { updateColumns(); } );
397 connect( mModel, &QgsAggregateMappingModel::modelReset, this, [this] { updateColumns(); } );
398 connect( mModel, &QgsAggregateMappingModel::dataChanged, this, &QgsAggregateMappingWidget::changed );
399 connect( mModel, &QgsAggregateMappingModel::rowsInserted, this, &QgsAggregateMappingWidget::changed );
400 connect( mModel, &QgsAggregateMappingModel::rowsRemoved, this, &QgsAggregateMappingWidget::changed );
401 connect( mModel, &QgsAggregateMappingModel::modelReset, this, &QgsAggregateMappingWidget::changed );
402}
403
405{
406 return qobject_cast<QgsAggregateMappingModel *>( mModel );
407}
408
409QList<QgsAggregateMappingModel::Aggregate> QgsAggregateMappingWidget::mapping() const
410{
411 return model()->mapping();
412}
413
414void QgsAggregateMappingWidget::setMapping( const QList<QgsAggregateMappingModel::Aggregate> &mapping )
415{
417}
418
420{
421 return mTableView->selectionModel();
422}
423
425{
426 model()->setSourceFields( sourceFields );
427}
428
430{
431 mSourceLayer = layer;
432}
433
435{
436 return mSourceLayer;
437}
438
439void QgsAggregateMappingWidget::scrollTo( const QModelIndex &index ) const
440{
441 mTableView->scrollTo( index );
442}
443
448
449void QgsAggregateMappingWidget::appendField( const QgsField &field, const QString &source, const QString &aggregate )
450{
451 model()->appendField( field, source, aggregate );
452}
453
455{
456 if ( !mTableView->selectionModel()->hasSelection() )
457 return false;
458
459 std::list<int> rowsToRemove { selectedRows() };
460 rowsToRemove.reverse();
461 for ( const int row : rowsToRemove )
462 {
463 if ( !model()->removeField( model()->index( row, 0, QModelIndex() ) ) )
464 {
465 return false;
466 }
467 }
468 return true;
469}
470
472{
473 if ( !mTableView->selectionModel()->hasSelection() )
474 return false;
475
476 const std::list<int> rowsToMoveUp { selectedRows() };
477 for ( const int row : rowsToMoveUp )
478 {
479 if ( !model()->moveUp( model()->index( row, 0, QModelIndex() ) ) )
480 {
481 return false;
482 }
483 }
484 return true;
485}
486
488{
489 if ( !mTableView->selectionModel()->hasSelection() )
490 return false;
491
492 std::list<int> rowsToMoveDown { selectedRows() };
493 rowsToMoveDown.reverse();
494 for ( const int row : rowsToMoveDown )
495 {
496 if ( !model()->moveDown( model()->index( row, 0, QModelIndex() ) ) )
497 {
498 return false;
499 }
500 }
501 return true;
502}
503
504void QgsAggregateMappingWidget::updateColumns()
505{
506 for ( int i = 0; i < mModel->rowCount(); ++i )
507 {
508 mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::SourceExpression ) ) );
509 mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::DestinationType ) ) );
510 mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::Aggregate ) ) );
511 }
512
513 for ( int i = 0; i < mModel->columnCount(); ++i )
514 {
515 mTableView->resizeColumnToContents( i );
516 }
517}
518
519std::list<int> QgsAggregateMappingWidget::selectedRows()
520{
521 std::list<int> rows;
522 if ( mTableView->selectionModel()->hasSelection() )
523 {
524 const QModelIndexList constSelection { mTableView->selectionModel()->selectedIndexes() };
525 for ( const QModelIndex &index : constSelection )
526 {
527 rows.push_back( index.row() );
528 }
529 rows.sort();
530 rows.unique();
531 }
532 return rows;
533}
534
535
537
538//
539// AggregateDelegate
540//
541
542QgsAggregateMappingDelegate::QgsAggregateMappingDelegate( QObject *parent )
543 : QStyledItemDelegate( parent )
544{
545}
546
547QWidget *QgsAggregateMappingDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex & ) const
548{
549 Q_UNUSED( option )
550 QComboBox *editor = new QComboBox( parent );
551
552 const QStringList aggregateList { aggregates() };
553 int i = 0;
554 for ( const QString &aggregate : aggregateList )
555 {
556 editor->addItem( aggregate );
557 editor->setItemData( i, aggregate, Qt::UserRole );
558 ++i;
559 }
560
561 connect( editor, qOverload<int>( &QComboBox::currentIndexChanged ), this, [this, editor]( int currentIndex ) {
562 Q_UNUSED( currentIndex )
563 const_cast<QgsAggregateMappingDelegate *>( this )->emit commitData( editor );
564 } );
565
566 return editor;
567}
568
569void QgsAggregateMappingDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
570{
571 QComboBox *editorWidget { qobject_cast<QComboBox *>( editor ) };
572 if ( !editorWidget )
573 return;
574
575 const QVariant value = index.model()->data( index, Qt::EditRole );
576 editorWidget->setCurrentIndex( editorWidget->findData( value ) );
577}
578
579void QgsAggregateMappingDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
580{
581 QComboBox *editorWidget { qobject_cast<QComboBox *>( editor ) };
582 if ( !editorWidget )
583 return;
584
585 const QVariant currentValue = editorWidget->currentData();
586 model->setData( index, currentValue, Qt::EditRole );
587}
588
589const QStringList QgsAggregateMappingDelegate::aggregates()
590{
591 static QStringList sAggregates;
592 static std::once_flag initialized;
593 std::call_once( initialized, []() {
594 sAggregates << QStringLiteral( "first_value" )
595 << QStringLiteral( "last_value" );
596
597 const QList<QgsExpressionFunction *> functions = QgsExpression::Functions();
598 for ( const QgsExpressionFunction *function : functions )
599 {
600 if ( !function || function->isDeprecated() || function->name().isEmpty() || function->name().at( 0 ) == '_' )
601 continue;
602
603 if ( function->groups().contains( QLatin1String( "Aggregates" ) ) )
604 {
605 if ( function->name() == QLatin1String( "aggregate" )
606 || function->name() == QLatin1String( "relation_aggregate" ) )
607 continue;
608
609 sAggregates.append( function->name() );
610 }
611
612 std::sort( sAggregates.begin(), sAggregates.end() );
613 }
614 } );
615
616 return sAggregates;
617}
618
Holds mapping information for defining sets of aggregates of fields from a QgsFields object.
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.
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.
An abstract base class for defining QgsExpression functions.
Handles 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).
Holds mapping information for mapping from one set of QgsFields to another.
static const QList< QgsVectorDataProvider::NativeType > supportedDataTypes()
Returns a static list of supported data types.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:54
QMetaType::Type type
Definition qgsfield.h:61
QString typeName() const
Gets the field type.
Definition qgsfield.cpp:163
int precision
Definition qgsfield.h:60
int length
Definition qgsfield.h:59
void setPrecision(int precision)
Set the field precision.
Definition qgsfield.cpp:263
void setSubType(QMetaType::Type subType)
If the field is a collection, set its element's type.
Definition qgsfield.cpp:244
void setName(const QString &name)
Set the field name.
Definition qgsfield.cpp:229
void setType(QMetaType::Type type)
Set variant type.
Definition qgsfield.cpp:234
void setLength(int len)
Set the field length.
Definition qgsfield.cpp:259
QString displayName() const
Returns the name to use when displaying this field.
Definition qgsfield.cpp:97
QMetaType::Type subType() const
If the field is a collection, gets its element's type.
Definition qgsfield.cpp:158
void setTypeName(const QString &typeName)
Set the field type.
Definition qgsfield.cpp:254
Container of fields for a vector layer.
Definition qgsfields.h:46
int count
Definition qgsfields.h:50
QgsPanelWidget(QWidget *parent=nullptr)
Base class for any widget that can be shown as an inline panel.
Represents a vector layer which manages a vector based dataset.
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).