QGIS API Documentation  3.20.0-Odense (decaadbb31)
qgsprocessingaggregatewidgetwrapper.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsprocessingaggregatewidgetwrapper.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 <QBoxLayout>
19 #include <QLineEdit>
20 #include <QMessageBox>
21 #include <QPushButton>
22 #include <QStandardItemModel>
23 #include <QToolButton>
24 #include <QItemSelectionModel>
25 
26 #include "qgspanelwidget.h"
27 
28 #include "qgsprocessingcontext.h"
29 #include "qgsprocessingmodelalgorithm.h"
30 
34 
36 
37 
38 //
39 // QgsProcessingAggregatePanelWidget
40 //
41 
42 
43 QgsProcessingAggregatePanelWidget::QgsProcessingAggregatePanelWidget( QWidget *parent )
44  : QgsPanelWidget( parent )
45 {
46  setupUi( this );
47 
48  mModel = mFieldsView->model();
49 
50  mLayerCombo->setAllowEmptyLayer( true );
51  mLayerCombo->setFilters( QgsMapLayerProxyModel::VectorLayer );
52 
53  connect( mResetButton, &QPushButton::clicked, this, &QgsProcessingAggregatePanelWidget::loadFieldsFromLayer );
54  connect( mAddButton, &QPushButton::clicked, this, &QgsProcessingAggregatePanelWidget::addField );
55  connect( mDeleteButton, &QPushButton::clicked, mFieldsView, &QgsAggregateMappingWidget::removeSelectedFields );
56  connect( mUpButton, &QPushButton::clicked, mFieldsView, &QgsAggregateMappingWidget::moveSelectedFieldsUp );
57  connect( mDownButton, &QPushButton::clicked, mFieldsView, &QgsAggregateMappingWidget::moveSelectedFieldsDown );
58  connect( mLoadLayerFieldsButton, &QPushButton::clicked, this, &QgsProcessingAggregatePanelWidget::loadLayerFields );
59 
60  connect( mFieldsView, &QgsAggregateMappingWidget::changed, this, [ = ]
61  {
62  if ( !mBlockChangedSignal )
63  {
64  emit changed();
65  }
66  } );
67 }
68 
69 void QgsProcessingAggregatePanelWidget::setLayer( QgsVectorLayer *layer )
70 {
71  if ( layer == mLayer )
72  return;
73 
74  mLayer = layer;
75  mFieldsView->setSourceLayer( mLayer );
76  if ( mModel->rowCount() == 0 )
77  {
78  loadFieldsFromLayer();
79  return;
80  }
81 
82  QMessageBox dlg( this );
83  dlg.setText( tr( "Do you want to reset the field mapping?" ) );
84  dlg.setStandardButtons(
85  QMessageBox::StandardButtons( QMessageBox::Yes |
86  QMessageBox::No ) );
87  dlg.setDefaultButton( QMessageBox::No );
88  if ( dlg.exec() == QMessageBox::Yes )
89  {
90  loadFieldsFromLayer();
91  }
92 }
93 
94 QgsVectorLayer *QgsProcessingAggregatePanelWidget::layer()
95 {
96  return mLayer;
97 }
98 
99 QVariant QgsProcessingAggregatePanelWidget::value() const
100 {
101  const QList<QgsAggregateMappingModel::Aggregate> mapping = mFieldsView->mapping();
102 
103  QVariantList results;
104  results.reserve( mapping.size() );
105  for ( const QgsAggregateMappingModel::Aggregate &aggregate : mapping )
106  {
107  QVariantMap def;
108  def.insert( QStringLiteral( "name" ), aggregate.field.name() );
109  def.insert( QStringLiteral( "type" ), static_cast< int >( aggregate.field.type() ) );
110  def.insert( QStringLiteral( "length" ), aggregate.field.length() );
111  def.insert( QStringLiteral( "precision" ), aggregate.field.precision() );
112  def.insert( QStringLiteral( "input" ), aggregate.source );
113  def.insert( QStringLiteral( "aggregate" ), aggregate.aggregate );
114  def.insert( QStringLiteral( "delimiter" ), aggregate.delimiter );
115  results.append( def );
116  }
117  return results;
118 }
119 
120 void QgsProcessingAggregatePanelWidget::setValue( const QVariant &value )
121 {
122  if ( value.type() != QVariant::List )
123  return;
124 
125  QList< QgsAggregateMappingModel::Aggregate > aggregates;
126 
127  const QVariantList fields = value.toList();
128  aggregates.reserve( fields.size() );
129  for ( const QVariant &field : fields )
130  {
131  const QVariantMap map = field.toMap();
132  QgsField f( map.value( QStringLiteral( "name" ) ).toString(),
133  static_cast< QVariant::Type >( map.value( QStringLiteral( "type" ), QVariant::Invalid ).toInt() ),
134  QVariant::typeToName( static_cast< QVariant::Type >( map.value( QStringLiteral( "type" ), QVariant::Invalid ).toInt() ) ),
135  map.value( QStringLiteral( "length" ), 0 ).toInt(),
136  map.value( QStringLiteral( "precision" ), 0 ).toInt() );
137 
139  aggregate.field = f;
140 
141  aggregate.source = map.value( QStringLiteral( "input" ) ).toString();
142  aggregate.aggregate = map.value( QStringLiteral( "aggregate" ) ).toString();
143  aggregate.delimiter = map.value( QStringLiteral( "delimiter" ) ).toString();
144 
145  aggregates.append( aggregate );
146  }
147 
148  mBlockChangedSignal = true;
149 
150  if ( aggregates.size() > 0 )
151  mFieldsView->setMapping( aggregates );
152 
153  mBlockChangedSignal = false;
154 
155  emit changed();
156 }
157 
158 void QgsProcessingAggregatePanelWidget::registerExpressionContextGenerator( const QgsExpressionContextGenerator *generator )
159 {
160  mFieldsView->registerExpressionContextGenerator( generator );
161 }
162 
163 void QgsProcessingAggregatePanelWidget::loadFieldsFromLayer()
164 {
165  if ( mLayer )
166  {
167  mFieldsView->setSourceFields( mLayer->fields() );
168  }
169 }
170 
171 void QgsProcessingAggregatePanelWidget::addField()
172 {
173  const int rowCount = mModel->rowCount();
174  mModel->appendField( QgsField( QStringLiteral( "new_field" ) ) );
175  QModelIndex index = mModel->index( rowCount, 0 );
176  mFieldsView->selectionModel()->select(
177  index,
178  QItemSelectionModel::SelectionFlags(
179  QItemSelectionModel::Clear |
180  QItemSelectionModel::Select |
181  QItemSelectionModel::Current |
182  QItemSelectionModel::Rows ) );
183  mFieldsView->scrollTo( index );
184 }
185 
186 void QgsProcessingAggregatePanelWidget::loadLayerFields()
187 {
188  if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( mLayerCombo->currentLayer() ) )
189  {
190  mFieldsView->setSourceFields( vl->fields() );
191  }
192 }
193 
194 //
195 // QgsProcessingAggregateParameterDefinitionWidget
196 //
197 
198 QgsProcessingAggregateParameterDefinitionWidget::QgsProcessingAggregateParameterDefinitionWidget( QgsProcessingContext &context, const QgsProcessingParameterWidgetContext &widgetContext, const QgsProcessingParameterDefinition *definition, const QgsProcessingAlgorithm *algorithm, QWidget *parent )
199  : QgsProcessingAbstractParameterDefinitionWidget( context, widgetContext, definition, algorithm, parent )
200 {
201  QVBoxLayout *vlayout = new QVBoxLayout();
202  vlayout->setContentsMargins( 0, 0, 0, 0 );
203 
204  vlayout->addWidget( new QLabel( tr( "Parent layer" ) ) );
205 
206  mParentLayerComboBox = new QComboBox();
207  mParentLayerComboBox->addItem( tr( "None" ), QVariant() );
208 
209  QString initialParent;
210  if ( const QgsProcessingParameterAggregate *aggregateParam = dynamic_cast<const QgsProcessingParameterAggregate *>( definition ) )
211  initialParent = aggregateParam->parentLayerParameterName();
212 
213  if ( auto *lModel = widgetContext.model() )
214  {
215  // populate combo box with other model input choices
216  const QMap<QString, QgsProcessingModelParameter> components = lModel->parameterComponents();
217  for ( auto it = components.constBegin(); it != components.constEnd(); ++it )
218  {
219  if ( const QgsProcessingParameterFeatureSource *definition = dynamic_cast< const QgsProcessingParameterFeatureSource * >( lModel->parameterDefinition( it.value().parameterName() ) ) )
220  {
221  mParentLayerComboBox-> addItem( definition->description(), definition->name() );
222  if ( !initialParent.isEmpty() && initialParent == definition->name() )
223  {
224  mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 );
225  }
226  }
227  else if ( const QgsProcessingParameterVectorLayer *definition = dynamic_cast< const QgsProcessingParameterVectorLayer * >( lModel->parameterDefinition( it.value().parameterName() ) ) )
228  {
229  mParentLayerComboBox-> addItem( definition->description(), definition->name() );
230  if ( !initialParent.isEmpty() && initialParent == definition->name() )
231  {
232  mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 );
233  }
234  }
235  }
236  }
237 
238  if ( mParentLayerComboBox->count() == 1 && !initialParent.isEmpty() )
239  {
240  // if no parent candidates found, we just add the existing one as a placeholder
241  mParentLayerComboBox->addItem( initialParent, initialParent );
242  mParentLayerComboBox->setCurrentIndex( mParentLayerComboBox->count() - 1 );
243  }
244 
245  vlayout->addWidget( mParentLayerComboBox );
246  setLayout( vlayout );
247 }
248 
249 QgsProcessingParameterDefinition *QgsProcessingAggregateParameterDefinitionWidget::createParameter( const QString &name, const QString &description, QgsProcessingParameterDefinition::Flags flags ) const
250 {
251  auto param = std::make_unique< QgsProcessingParameterAggregate >( name, description, mParentLayerComboBox->currentData().toString() );
252  param->setFlags( flags );
253  return param.release();
254 }
255 
256 //
257 // QgsProcessingAggregateWidgetWrapper
258 //
259 
260 QgsProcessingAggregateWidgetWrapper::QgsProcessingAggregateWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type, QWidget *parent )
261  : QgsAbstractProcessingParameterWidgetWrapper( parameter, type, parent )
262 {
263 }
264 
265 QString QgsProcessingAggregateWidgetWrapper::parameterType() const
266 {
268 }
269 
270 QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingAggregateWidgetWrapper::createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type )
271 {
272  return new QgsProcessingAggregateWidgetWrapper( parameter, type );
273 }
274 
275 QWidget *QgsProcessingAggregateWidgetWrapper::createWidget()
276 {
277  mPanel = new QgsProcessingAggregatePanelWidget( nullptr );
278  mPanel->setToolTip( parameterDefinition()->toolTip() );
279  mPanel->registerExpressionContextGenerator( this );
280 
281  connect( mPanel, &QgsProcessingAggregatePanelWidget::changed, this, [ = ]
282  {
283  emit widgetValueHasChanged( this );
284  } );
285 
286  return mPanel;
287 }
288 
289 QgsProcessingAbstractParameterDefinitionWidget *QgsProcessingAggregateWidgetWrapper::createParameterDefinitionWidget( QgsProcessingContext &context, const QgsProcessingParameterWidgetContext &widgetContext, const QgsProcessingParameterDefinition *definition, const QgsProcessingAlgorithm *algorithm )
290 {
291  return new QgsProcessingAggregateParameterDefinitionWidget( context, widgetContext, definition, algorithm );
292 }
293 
294 void QgsProcessingAggregateWidgetWrapper::postInitialize( const QList<QgsAbstractProcessingParameterWidgetWrapper *> &wrappers )
295 {
297  switch ( type() )
298  {
301  {
302  for ( const QgsAbstractProcessingParameterWidgetWrapper *wrapper : wrappers )
303  {
304  if ( wrapper->parameterDefinition()->name() == static_cast< const QgsProcessingParameterAggregate * >( parameterDefinition() )->parentLayerParameterName() )
305  {
306  setParentLayerWrapperValue( wrapper );
308  {
309  setParentLayerWrapperValue( wrapper );
310  } );
311  break;
312  }
313  }
314  break;
315  }
316 
318  break;
319  }
320 }
321 
322 int QgsProcessingAggregateWidgetWrapper::stretch() const
323 {
324  return 1;
325 }
326 
327 void QgsProcessingAggregateWidgetWrapper::setParentLayerWrapperValue( const QgsAbstractProcessingParameterWidgetWrapper *parentWrapper )
328 {
329  // evaluate value to layer
330  QgsProcessingContext *context = nullptr;
331  std::unique_ptr< QgsProcessingContext > tmpContext;
332  if ( mProcessingContextGenerator )
333  context = mProcessingContextGenerator->processingContext();
334 
335  if ( !context )
336  {
337  tmpContext = std::make_unique< QgsProcessingContext >();
338  context = tmpContext.get();
339  }
340 
341  QgsVectorLayer *layer = QgsProcessingParameters::parameterAsVectorLayer( parentWrapper->parameterDefinition(), parentWrapper->parameterValue(), *context );
342  if ( !layer )
343  {
344  if ( mPanel )
345  mPanel->setLayer( nullptr );
346  return;
347  }
348 
349  // need to grab ownership of layer if required - otherwise layer may be deleted when context
350  // goes out of scope
351  std::unique_ptr< QgsMapLayer > ownedLayer( context->takeResultLayer( layer->id() ) );
352  if ( ownedLayer && ownedLayer->type() == QgsMapLayerType::VectorLayer )
353  {
354  mParentLayer.reset( qobject_cast< QgsVectorLayer * >( ownedLayer.release() ) );
355  layer = mParentLayer.get();
356  }
357  else
358  {
359  // don't need ownership of this layer - it wasn't owned by context (so e.g. is owned by the project)
360  }
361 
362  if ( mPanel )
363  mPanel->setLayer( layer );
364 }
365 
366 void QgsProcessingAggregateWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext & )
367 {
368  if ( mPanel )
369  mPanel->setValue( value );
370 }
371 
372 QVariant QgsProcessingAggregateWidgetWrapper::widgetValue() const
373 {
374  return mPanel ? mPanel->value() : QVariant();
375 }
376 
377 QStringList QgsProcessingAggregateWidgetWrapper::compatibleParameterTypes() const
378 {
379  return QStringList()
381 }
382 
383 QStringList QgsProcessingAggregateWidgetWrapper::compatibleOutputTypes() const
384 {
385  return QStringList();
386 }
387 
388 QString QgsProcessingAggregateWidgetWrapper::modelerExpressionFormatString() const
389 {
390  return tr( "an array of map items, each containing a 'name', 'type', 'aggregate' and 'input' value (and optional 'length' and 'precision' values)." );
391 }
392 
393 const QgsVectorLayer *QgsProcessingAggregateWidgetWrapper::linkedVectorLayer() const
394 {
395  if ( mPanel && mPanel->layer() )
396  return mPanel->layer();
397 
399 }
400 
402 
403 
A widget wrapper for Processing parameter value widgets.
QVariant parameterValue() const
Returns the current value of the parameter.
void widgetValueHasChanged(QgsAbstractProcessingParameterWidgetWrapper *wrapper)
Emitted whenever the parameter value (as defined by the wrapped widget) is changed.
virtual void postInitialize(const QList< QgsAbstractProcessingParameterWidgetWrapper * > &wrappers)
Called after all wrappers have been created within a particular dialog or context,...
virtual const QgsVectorLayer * linkedVectorLayer() const
Returns the optional vector layer associated with this widget wrapper, or nullptr if no vector layer ...
const QgsProcessingParameterDefinition * parameterDefinition() const
Returns the parameter definition associated with this wrapper.
void changed()
Emitted when the aggregates defined in the widget are changed.
bool moveSelectedFieldsDown()
Moves down currently selected field.
bool moveSelectedFieldsUp()
Moves up currently selected field.
bool removeSelectedFields()
Removes the currently selected field from the model.
Abstract interface for generating an expression context.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
Base class for any widget that can be shown as a inline panel.
Abstract base class for widgets which allow users to specify the properties of a Processing parameter...
Abstract base class for processing algorithms.
Contains information about the context in which a processing algorithm is executed.
QgsMapLayer * takeResultLayer(const QString &id)
Takes the result map layer with matching id from the context and transfers ownership of it back to th...
WidgetType
Types of dialogs which Processing widgets can be created for.
@ Modeler
Modeler dialog.
@ Standard
Standard algorithm dialog.
@ Batch
Batch processing dialog.
A parameter for "aggregate" configurations, which consist of a definition of desired output fields,...
static QString typeName()
Returns the type name for the parameter class.
Base class for the definition of processing parameters.
QString description() const
Returns the description for the parameter.
QString name() const
Returns the name of the parameter.
An input feature source (such as vector layers) parameter for processing algorithms.
A vector layer (with or without geometry) parameter for processing algorithms.
Contains settings which reflect the context in which a Processing parameter widget is shown,...
QgsProcessingModelAlgorithm * model() const
Returns the model which the parameter widget is associated with.
static QgsVectorLayer * parameterAsVectorLayer(const QgsProcessingParameterDefinition *definition, const QVariantMap &parameters, QgsProcessingContext &context)
Evaluates the parameter with matching definition to a vector layer.
Represents a vector layer which manages a vector based data sets.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into allowing algorithms to be written in pure substantial changes are required in order to port existing x Processing algorithms for QGIS x The most significant changes are outlined not GeoAlgorithm For algorithms which operate on features one by consider subclassing the QgsProcessingFeatureBasedAlgorithm class This class allows much of the boilerplate code for looping over features from a vector layer to be bypassed and instead requires implementation of a processFeature method Ensure that your algorithm(or algorithm 's parent class) implements the new pure virtual createInstance(self) call
const QgsField & field
Definition: qgsfield.h:463
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)