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