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