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