QGIS API Documentation 3.43.0-Master (9e873c7bc91)
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#include "moc_qgsprocessingaggregatewidgets.cpp"
21
22#include <QBoxLayout>
23#include <QLineEdit>
24#include <QMessageBox>
25#include <QPushButton>
26#include <QStandardItemModel>
27#include <QToolButton>
28#include <QTableView>
29#include <mutex>
30
31
32//
33// QgsAggregateMappingModel
34//
35
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 | Qt::ItemIsEditable | Qt::ItemIsEnabled );
167 }
168 return Qt::ItemFlags();
169}
170
171bool QgsAggregateMappingModel::setData( const QModelIndex &index, const QVariant &value, int role )
172{
173 if ( index.isValid() )
174 {
175 if ( role == Qt::EditRole )
176 {
177 Aggregate &f = mMapping[index.row()];
178 switch ( static_cast<ColumnDataIndex>( index.column() ) )
179 {
181 {
182 const QgsExpression exp { value.toString() };
183 f.source = exp;
184 break;
185 }
187 {
188 f.aggregate = value.toString();
189 break;
190 }
192 {
193 f.delimiter = value.toString();
194 break;
195 }
197 {
198 f.field.setName( value.toString() );
199 break;
200 }
202 {
203 setFieldTypeFromName( f.field, value.toString() );
204 break;
205 }
207 {
208 bool ok;
209 const int length { value.toInt( &ok ) };
210 if ( ok )
211 f.field.setLength( length );
212 break;
213 }
215 {
216 bool ok;
217 const int precision { value.toInt( &ok ) };
218 if ( ok )
220 break;
221 }
222 }
223 emit dataChanged( index, index );
224 }
225 return true;
226 }
227 else
228 {
229 return false;
230 }
231}
232
233
234bool QgsAggregateMappingModel::moveUpOrDown( const QModelIndex &index, bool up )
235{
236 if ( !index.isValid() && index.model() == this )
237 return false;
238
239 // Always swap down
240 const int row { up ? index.row() - 1 : index.row() };
241 // Range checking
242 if ( row < 0 || row + 1 >= rowCount( QModelIndex() ) )
243 {
244 return false;
245 }
246 beginMoveRows( QModelIndex(), row, row, QModelIndex(), row + 2 );
247 mMapping.swapItemsAt( row, row + 1 );
248 endMoveRows();
249 return true;
250}
251
252QString QgsAggregateMappingModel::qgsFieldToTypeName( const QgsField &field )
253{
254 const QList<QgsVectorDataProvider::NativeType> types = QgsFieldMappingModel::supportedDataTypes();
255 for ( const QgsVectorDataProvider::NativeType &type : types )
256 {
257 if ( type.mType == field.type() && type.mSubType == field.subType() )
258 {
259 return type.mTypeName;
260 }
261 }
262 return QString();
263}
264
265void QgsAggregateMappingModel::setFieldTypeFromName( QgsField &field, const QString &name )
266{
267 const QList<QgsVectorDataProvider::NativeType> types = QgsFieldMappingModel::supportedDataTypes();
268 for ( const QgsVectorDataProvider::NativeType &type : types )
269 {
270 if ( type.mTypeName == name )
271 {
272 field.setType( type.mType );
273 field.setTypeName( type.mTypeName );
274 field.setSubType( type.mSubType );
275 return;
276 }
277 }
278}
279
281{
282 mSourceFields = sourceFields;
283 if ( mExpressionContextGenerator )
284 mExpressionContextGenerator->setSourceFields( mSourceFields );
285
286 beginResetModel();
287 mMapping.clear();
288
289 for ( const QgsField &f : sourceFields )
290 {
291 Aggregate aggregate;
292 aggregate.field = f;
293 aggregate.field.setTypeName( qgsFieldToTypeName( f ) );
294 aggregate.source = QgsExpression::quotedColumnRef( f.name() );
295
296 if ( f.isNumeric() )
297 aggregate.aggregate = QStringLiteral( "sum" );
298 else if ( f.type() == QMetaType::Type::QString || ( f.type() == QMetaType::Type::QVariantList && f.subType() == QMetaType::Type::QString ) )
299 aggregate.aggregate = QStringLiteral( "concatenate" );
300
301 aggregate.delimiter = ',';
302
303 mMapping.push_back( aggregate );
304 }
305 endResetModel();
306}
307
309{
310 return mExpressionContextGenerator.get();
311}
312
314{
315 mExpressionContextGenerator->setBaseExpressionContextGenerator( generator );
316}
317
318QList<QgsAggregateMappingModel::Aggregate> QgsAggregateMappingModel::mapping() const
319{
320 return mMapping;
321}
322
323void QgsAggregateMappingModel::setMapping( const QList<QgsAggregateMappingModel::Aggregate> &mapping )
324{
325 beginResetModel();
326 mMapping = mapping;
327 for ( auto &agg : mMapping )
328 {
329 agg.field.setTypeName( qgsFieldToTypeName( agg.field ) );
330 }
331 endResetModel();
332}
333
334void QgsAggregateMappingModel::appendField( const QgsField &field, const QString &source, const QString &aggregate )
335{
336 const int lastRow { rowCount( QModelIndex() ) };
337 beginInsertRows( QModelIndex(), lastRow, lastRow );
338 Aggregate agg;
339 agg.field = field;
340 agg.field.setTypeName( qgsFieldToTypeName( field ) );
341 agg.source = source;
342 agg.aggregate = aggregate;
343 agg.delimiter = ',';
344 mMapping.push_back( agg );
345 endInsertRows();
346}
347
348bool QgsAggregateMappingModel::removeField( const QModelIndex &index )
349{
350 if ( index.isValid() && index.model() == this && index.row() < rowCount( QModelIndex() ) )
351 {
352 beginRemoveRows( QModelIndex(), index.row(), index.row() );
353 mMapping.removeAt( index.row() );
354 endRemoveRows();
355 return true;
356 }
357 else
358 {
359 return false;
360 }
361}
362
363bool QgsAggregateMappingModel::moveUp( const QModelIndex &index )
364{
365 return moveUpOrDown( index );
366}
367
368bool QgsAggregateMappingModel::moveDown( const QModelIndex &index )
369{
370 return moveUpOrDown( index, false );
371}
372
373
374//
375// QgsAggregateMappingWidget
376//
377
379 : QgsPanelWidget( parent )
380{
381 QVBoxLayout *verticalLayout = new QVBoxLayout();
382 verticalLayout->setContentsMargins( 0, 0, 0, 0 );
383 mTableView = new QTableView();
384 verticalLayout->addWidget( mTableView );
385 setLayout( verticalLayout );
386
387 mModel = new QgsAggregateMappingModel( sourceFields, this );
388 mTableView->setModel( mModel );
389 mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::SourceExpression ), new QgsFieldMappingExpressionDelegate( this ) );
390 mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::Aggregate ), new QgsAggregateMappingDelegate( mTableView ) );
391 mTableView->setItemDelegateForColumn( static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::DestinationType ), new QgsFieldMappingTypeDelegate( {}, mTableView ) );
392 updateColumns();
393 // Make sure columns are updated when rows are added
394 connect( mModel, &QgsAggregateMappingModel::rowsInserted, this, [=] { updateColumns(); } );
395 connect( mModel, &QgsAggregateMappingModel::modelReset, this, [=] { updateColumns(); } );
396 connect( mModel, &QgsAggregateMappingModel::dataChanged, this, &QgsAggregateMappingWidget::changed );
397 connect( mModel, &QgsAggregateMappingModel::rowsInserted, this, &QgsAggregateMappingWidget::changed );
398 connect( mModel, &QgsAggregateMappingModel::rowsRemoved, this, &QgsAggregateMappingWidget::changed );
399 connect( mModel, &QgsAggregateMappingModel::modelReset, this, &QgsAggregateMappingWidget::changed );
400}
401
403{
404 return qobject_cast<QgsAggregateMappingModel *>( mModel );
405}
406
407QList<QgsAggregateMappingModel::Aggregate> QgsAggregateMappingWidget::mapping() const
408{
409 return model()->mapping();
410}
411
412void QgsAggregateMappingWidget::setMapping( const QList<QgsAggregateMappingModel::Aggregate> &mapping )
413{
415}
416
418{
419 return mTableView->selectionModel();
420}
421
423{
424 model()->setSourceFields( sourceFields );
425}
426
428{
429 mSourceLayer = layer;
430}
431
433{
434 return mSourceLayer;
435}
436
437void QgsAggregateMappingWidget::scrollTo( const QModelIndex &index ) const
438{
439 mTableView->scrollTo( index );
440}
441
446
447void QgsAggregateMappingWidget::appendField( const QgsField &field, const QString &source, const QString &aggregate )
448{
449 model()->appendField( field, source, aggregate );
450}
451
453{
454 if ( !mTableView->selectionModel()->hasSelection() )
455 return false;
456
457 std::list<int> rowsToRemove { selectedRows() };
458 rowsToRemove.reverse();
459 for ( const int row : rowsToRemove )
460 {
461 if ( !model()->removeField( model()->index( row, 0, QModelIndex() ) ) )
462 {
463 return false;
464 }
465 }
466 return true;
467}
468
470{
471 if ( !mTableView->selectionModel()->hasSelection() )
472 return false;
473
474 const std::list<int> rowsToMoveUp { selectedRows() };
475 for ( const int row : rowsToMoveUp )
476 {
477 if ( !model()->moveUp( model()->index( row, 0, QModelIndex() ) ) )
478 {
479 return false;
480 }
481 }
482 return true;
483}
484
486{
487 if ( !mTableView->selectionModel()->hasSelection() )
488 return false;
489
490 std::list<int> rowsToMoveDown { selectedRows() };
491 rowsToMoveDown.reverse();
492 for ( const int row : rowsToMoveDown )
493 {
494 if ( !model()->moveDown( model()->index( row, 0, QModelIndex() ) ) )
495 {
496 return false;
497 }
498 }
499 return true;
500}
501
502void QgsAggregateMappingWidget::updateColumns()
503{
504 for ( int i = 0; i < mModel->rowCount(); ++i )
505 {
506 mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::SourceExpression ) ) );
507 mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::DestinationType ) ) );
508 mTableView->openPersistentEditor( mModel->index( i, static_cast<int>( QgsAggregateMappingModel::ColumnDataIndex::Aggregate ) ) );
509 }
510
511 for ( int i = 0; i < mModel->columnCount(); ++i )
512 {
513 mTableView->resizeColumnToContents( i );
514 }
515}
516
517std::list<int> QgsAggregateMappingWidget::selectedRows()
518{
519 std::list<int> rows;
520 if ( mTableView->selectionModel()->hasSelection() )
521 {
522 const QModelIndexList constSelection { mTableView->selectionModel()->selectedIndexes() };
523 for ( const QModelIndex &index : constSelection )
524 {
525 rows.push_back( index.row() );
526 }
527 rows.sort();
528 rows.unique();
529 }
530 return rows;
531}
532
533
535
536//
537// AggregateDelegate
538//
539
540QgsAggregateMappingDelegate::QgsAggregateMappingDelegate( QObject *parent )
541 : QStyledItemDelegate( parent )
542{
543}
544
545QWidget *QgsAggregateMappingDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex & ) const
546{
547 Q_UNUSED( option )
548 QComboBox *editor = new QComboBox( parent );
549
550 const QStringList aggregateList { aggregates() };
551 int i = 0;
552 for ( const QString &aggregate : aggregateList )
553 {
554 editor->addItem( aggregate );
555 editor->setItemData( i, aggregate, Qt::UserRole );
556 ++i;
557 }
558
559 connect( editor, qOverload<int>( &QComboBox::currentIndexChanged ), this, [=]( int currentIndex ) {
560 Q_UNUSED( currentIndex )
561 const_cast<QgsAggregateMappingDelegate *>( this )->emit commitData( editor );
562 } );
563
564 return editor;
565}
566
567void QgsAggregateMappingDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
568{
569 QComboBox *editorWidget { qobject_cast<QComboBox *>( editor ) };
570 if ( !editorWidget )
571 return;
572
573 const QVariant value = index.model()->data( index, Qt::EditRole );
574 editorWidget->setCurrentIndex( editorWidget->findData( value ) );
575}
576
577void QgsAggregateMappingDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
578{
579 QComboBox *editorWidget { qobject_cast<QComboBox *>( editor ) };
580 if ( !editorWidget )
581 return;
582
583 const QVariant currentValue = editorWidget->currentData();
584 model->setData( index, currentValue, Qt::EditRole );
585}
586
587const QStringList QgsAggregateMappingDelegate::aggregates()
588{
589 static QStringList sAggregates;
590 static std::once_flag initialized;
591 std::call_once( initialized, [=]() {
592 sAggregates << QStringLiteral( "first_value" )
593 << QStringLiteral( "last_value" );
594
595 const QList<QgsExpressionFunction *> functions = QgsExpression::Functions();
596 for ( const QgsExpressionFunction *function : functions )
597 {
598 if ( !function || function->isDeprecated() || function->name().isEmpty() || function->name().at( 0 ) == '_' )
599 continue;
600
601 if ( function->groups().contains( QLatin1String( "Aggregates" ) ) )
602 {
603 if ( function->name() == QLatin1String( "aggregate" )
604 || function->name() == QLatin1String( "relation_aggregate" ) )
605 continue;
606
607 sAggregates.append( function->name() );
608 }
609
610 std::sort( sAggregates.begin(), sAggregates.end() );
611 }
612 } );
613
614 return sAggregates;
615}
616
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,...
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:53
QMetaType::Type type
Definition qgsfield.h:60
void setPrecision(int precision)
Set the field precision.
Definition qgsfield.cpp:262
void setSubType(QMetaType::Type subType)
If the field is a collection, set its element's type.
Definition qgsfield.cpp:243
void setName(const QString &name)
Set the field name.
Definition qgsfield.cpp:228
void setType(QMetaType::Type type)
Set variant type.
Definition qgsfield.cpp:233
void setLength(int len)
Set the field length.
Definition qgsfield.cpp:258
QMetaType::Type subType() const
If the field is a collection, gets its element's type.
Definition qgsfield.cpp:157
void setTypeName(const QString &typeName)
Set the field type.
Definition qgsfield.cpp:253
Container of fields for a vector layer.
Definition qgsfields.h:46
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)