QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgsvaluerelationwidgetwrapper.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsvaluerelationwidgetwrapper.cpp
3  --------------------------------------
4  Date : 5.1.2014
5  Copyright : (C) 2014 Matthias Kuhn
6  Email : matthias at opengis dot ch
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 "qgis.h"
19 #include "qgsfields.h"
20 #include "qgsproject.h"
22 #include "qgsvectorlayer.h"
23 #include "qgsfilterlineedit.h"
24 #include "qgsfeatureiterator.h"
26 #include "qgsattributeform.h"
27 #include "qgsattributes.h"
28 #include "qgsjsonutils.h"
29 #include "qgspostgresstringutils.h"
30 
31 #include <QHeaderView>
32 #include <QComboBox>
33 #include <QLineEdit>
34 #include <QTableWidget>
35 #include <QStringListModel>
36 #include <QCompleter>
37 #include <QTimer>
38 
39 #include <nlohmann/json.hpp>
40 using namespace nlohmann;
41 
42 
43 QgsValueRelationWidgetWrapper::QgsValueRelationWidgetWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor, QWidget *parent )
44  : QgsEditorWidgetWrapper( layer, fieldIdx, editor, parent )
45 {
46 }
47 
48 
50 {
51  QVariant v;
52 
53  if ( mComboBox )
54  {
55  int cbxIdx = mComboBox->currentIndex();
56  if ( cbxIdx > -1 )
57  {
58  v = mComboBox->currentData();
59  }
60  }
61 
62  const int nofColumns = columnCount();
63 
64  if ( mTableWidget )
65  {
66  QStringList selection;
67  for ( int j = 0; j < mTableWidget->rowCount(); j++ )
68  {
69  for ( int i = 0; i < nofColumns; ++i )
70  {
71  QTableWidgetItem *item = mTableWidget->item( j, i );
72  if ( item )
73  {
74  if ( item->checkState() == Qt::Checked )
75  selection << item->data( Qt::UserRole ).toString();
76  }
77  }
78  }
79 
80  // If there is no selection and allow NULL is not checked return NULL.
81  if ( selection.isEmpty() && ! config( QStringLiteral( "AllowNull" ) ).toBool( ) )
82  {
83  return QVariant( QVariant::Type::List );
84  }
85 
86  QVariantList vl;
87  //store as QVariantList because the field type supports data structure
88  for ( const QString &s : std::as_const( selection ) )
89  {
90  // Convert to proper type
91  const QVariant::Type type { fkType() };
92  switch ( type )
93  {
94  case QVariant::Type::Int:
95  vl.push_back( s.toInt() );
96  break;
97  case QVariant::Type::LongLong:
98  vl.push_back( s.toLongLong() );
99  break;
100  default:
101  vl.push_back( s );
102  break;
103  }
104  }
105 
106  if ( layer()->fields().at( fieldIdx() ).type() == QVariant::Map ||
107  layer()->fields().at( fieldIdx() ).type() == QVariant::List )
108  {
109  v = vl;
110  }
111  else
112  {
113  //make string
115  }
116  }
117 
118  if ( mLineEdit )
119  {
120  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : std::as_const( mCache ) )
121  {
122  if ( item.value == mLineEdit->text() )
123  {
124  v = item.key;
125  break;
126  }
127  }
128  }
129 
130  return v;
131 }
132 
134 {
135  QgsAttributeForm *form = qobject_cast<QgsAttributeForm *>( parent );
136  if ( form )
138 
139  mExpression = config().value( QStringLiteral( "FilterExpression" ) ).toString();
140 
141  if ( config( QStringLiteral( "AllowMulti" ) ).toBool() )
142  {
143  return new QTableWidget( parent );
144  }
145  else if ( config( QStringLiteral( "UseCompleter" ) ).toBool() )
146  {
147  return new QgsFilterLineEdit( parent );
148  }
149  else
150  {
151  return new QComboBox( parent );
152  }
153 }
154 
156 {
157 
158  mComboBox = qobject_cast<QComboBox *>( editor );
159  mTableWidget = qobject_cast<QTableWidget *>( editor );
160  mLineEdit = qobject_cast<QLineEdit *>( editor );
161 
162  // Read current initial form values from the editor context
164 
165  if ( mComboBox )
166  {
167  connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
168  this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
169  }
170  else if ( mTableWidget )
171  {
172  mTableWidget->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );
173  mTableWidget->horizontalHeader()->setVisible( false );
174  mTableWidget->verticalHeader()->setSectionResizeMode( QHeaderView::Stretch );
175  mTableWidget->verticalHeader()->setVisible( false );
176  mTableWidget->setShowGrid( false );
177  mTableWidget->setEditTriggers( QAbstractItemView::NoEditTriggers );
178  mTableWidget->setSelectionMode( QAbstractItemView::NoSelection );
179  connect( mTableWidget, &QTableWidget::itemChanged, this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
180  }
181  else if ( mLineEdit )
182  {
183  connect( mLineEdit, &QLineEdit::textChanged, this, &QgsValueRelationWidgetWrapper::emitValueChangedInternal, Qt::UniqueConnection );
184  }
185 }
186 
188 {
189  return mTableWidget || mLineEdit || mComboBox;
190 }
191 
192 void QgsValueRelationWidgetWrapper::updateValues( const QVariant &value, const QVariantList & )
193 {
194  if ( mTableWidget )
195  {
196  QStringList checkList;
197 
198  if ( layer()->fields().at( fieldIdx() ).type() == QVariant::Map ||
199  layer()->fields().at( fieldIdx() ).type() == QVariant::List )
200  {
201  checkList = value.toStringList();
202  }
203  else
204  {
206  }
207 
208  QTableWidgetItem *lastChangedItem = nullptr;
209 
210  const int nofColumns = columnCount();
211 
212  // This block is needed because item->setCheckState triggers dataChanged gets back to value()
213  // and iterate over all items again! This can be extremely slow on large items sets.
214  for ( int j = 0; j < mTableWidget->rowCount(); j++ )
215  {
216  auto signalBlockedTableWidget = whileBlocking( mTableWidget );
217  Q_UNUSED( signalBlockedTableWidget )
218 
219  for ( int i = 0; i < nofColumns; ++i )
220  {
221  QTableWidgetItem *item = mTableWidget->item( j, i );
222  if ( item )
223  {
224  item->setCheckState( checkList.contains( item->data( Qt::UserRole ).toString() ) ? Qt::Checked : Qt::Unchecked );
225  //re-set enabled state because it's lost after reloading items
226  item->setFlags( mEnabled ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled );
227  lastChangedItem = item;
228  }
229  }
230  }
231  // let's trigger the signal now, once and for all
232  if ( lastChangedItem )
233  lastChangedItem->setCheckState( checkList.contains( lastChangedItem->data( Qt::UserRole ).toString() ) ? Qt::Checked : Qt::Unchecked );
234 
235  }
236  else if ( mComboBox )
237  {
238  // findData fails to tell a 0 from a NULL
239  // See: "Value relation, value 0 = NULL" - https://github.com/qgis/QGIS/issues/27803
240  int idx = -1; // default to not found
241  for ( int i = 0; i < mComboBox->count(); i++ )
242  {
243  QVariant v( mComboBox->itemData( i ) );
244  if ( qgsVariantEqual( v, value ) )
245  {
246  idx = i;
247  break;
248  }
249  }
250  mComboBox->setCurrentIndex( idx );
251  }
252  else if ( mLineEdit )
253  {
254  mLineEdit->clear();
255  bool wasFound { false };
256  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : std::as_const( mCache ) )
257  {
258  if ( i.key == value )
259  {
260  mLineEdit->setText( i.value );
261  wasFound = true;
262  break;
263  }
264  }
265  // Value could not be found
266  if ( ! wasFound )
267  {
268  mLineEdit->setText( tr( "(no selection)" ) );
269  }
270  }
271 }
272 
273 void QgsValueRelationWidgetWrapper::widgetValueChanged( const QString &attribute, const QVariant &newValue, bool attributeChanged )
274 {
275 
276  // Do nothing if the value has not changed
277  if ( attributeChanged )
278  {
279  QVariant oldValue( value( ) );
280  setFormFeatureAttribute( attribute, newValue );
281  // Update combos if the value used in the filter expression has changed
283  && QgsValueRelationFieldFormatter::expressionFormAttributes( mExpression ).contains( attribute ) )
284  {
285  populate();
286  // Restore value
287  updateValues( value( ) );
288  // If the value has changed as a result of another widget's value change,
289  // we need to emit the signal to make sure other dependent widgets are
290  // updated.
291  QgsFields formFields( formFeature().fields() );
292 
293  // Also check for fields in the layer in case this is a multi-edit form
294  // and there is not form feature set
295  if ( formFields.count() == 0 && layer() )
296  {
297  formFields = layer()->fields();
298  }
299 
300  if ( oldValue != value() && fieldIdx() < formFields.count() )
301  {
302  QString attributeName( formFields.names().at( fieldIdx() ) );
303  setFormFeatureAttribute( attributeName, value( ) );
305  }
306  }
307  }
308 }
309 
310 
312 {
313  setFormFeature( feature );
314  whileBlocking( this )->populate();
315  whileBlocking( this )->setValue( feature.attribute( fieldIdx() ) );
316 
317  // As we block any signals, possible depending widgets will not being updated
318  // so we force emit signal once and for all
320 
321  // A bit of logic to set the default value if AllowNull is false and this is a new feature
322  // Note that this needs to be here after the cache has been created/updated by populate()
323  // and signals unblocked (we want this to propagate to the feature itself)
324  if ( context().attributeFormMode() != QgsAttributeEditorContext::Mode::MultiEditMode
325  && ! formFeature().attribute( fieldIdx() ).isValid()
326  && ! mCache.isEmpty()
327  && ! config( QStringLiteral( "AllowNull" ) ).toBool( ) )
328  {
329  // This is deferred because at the time the feature is set in one widget it is not
330  // set in the next, which is typically the "down" in a drill-down
331  QTimer::singleShot( 0, this, [ this ]
332  {
333  if ( ! mCache.isEmpty() )
334  {
335  updateValues( formFeature().attribute( fieldIdx() ).isValid() ? formFeature().attribute( fieldIdx() ) : mCache.at( 0 ).key );
336  }
337  } );
338  }
339 }
340 
341 int QgsValueRelationWidgetWrapper::columnCount() const
342 {
343  return std::max( 1, config( QStringLiteral( "NofColumns" ) ).toInt() );
344 }
345 
346 
347 QVariant::Type QgsValueRelationWidgetWrapper::fkType() const
348 {
350  if ( layer )
351  {
352  QgsFields fields = layer->fields();
353  int idx { fields.lookupField( config().value( QStringLiteral( "Key" ) ).toString() ) };
354  if ( idx >= 0 )
355  {
356  return fields.at( idx ).type();
357  }
358  }
359  return QVariant::Type::Invalid;
360 }
361 
362 void QgsValueRelationWidgetWrapper::populate( )
363 {
364  // Initialize, note that signals are blocked, to avoid double signals on new features
367  {
368  if ( context().parentFormFeature().isValid() )
369  {
370  mCache = QgsValueRelationFieldFormatter::createCache( config(), formFeature(), context().parentFormFeature() );
371  }
372  else
373  {
375  }
376  }
377  else if ( mCache.empty() )
378  {
380  }
381 
382  if ( mComboBox )
383  {
384  mComboBox->clear();
385  if ( config( QStringLiteral( "AllowNull" ) ).toBool( ) )
386  {
387  whileBlocking( mComboBox )->addItem( tr( "(no selection)" ), QVariant( field().type( ) ) );
388  }
389 
390  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : std::as_const( mCache ) )
391  {
392  whileBlocking( mComboBox )->addItem( element.value, element.key );
393  if ( !element.description.isEmpty() )
394  mComboBox->setItemData( mComboBox->count() - 1, element.description, Qt::ToolTipRole );
395  }
396 
397  }
398  else if ( mTableWidget )
399  {
400  const int nofColumns = columnCount();
401 
402  if ( ! mCache.empty() )
403  {
404  mTableWidget->setRowCount( ( mCache.size() + nofColumns - 1 ) / nofColumns );
405  }
406  else
407  mTableWidget->setRowCount( 1 );
408  mTableWidget->setColumnCount( nofColumns );
409 
410  whileBlocking( mTableWidget )->clear();
411  int row = 0;
412  int column = 0;
413  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : std::as_const( mCache ) )
414  {
415  if ( column == nofColumns )
416  {
417  row++;
418  column = 0;
419  }
420  QTableWidgetItem *item = nullptr;
421  item = new QTableWidgetItem( element.value );
422  item->setData( Qt::UserRole, element.key );
423  whileBlocking( mTableWidget )->setItem( row, column, item );
424  column++;
425  }
426 
427  }
428  else if ( mLineEdit )
429  {
430  QStringList values;
431  values.reserve( mCache.size() );
432  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : std::as_const( mCache ) )
433  {
434  values << i.value;
435  }
436  QStringListModel *m = new QStringListModel( values, mLineEdit );
437  QCompleter *completer = new QCompleter( m, mLineEdit );
438  completer->setCaseSensitivity( Qt::CaseInsensitive );
439  mLineEdit->setCompleter( completer );
440  }
441 }
442 
444 {
445  const int nofColumns = columnCount();
446 
447  if ( mTableWidget )
448  {
449  for ( int j = 0; j < mTableWidget->rowCount(); j++ )
450  {
451  for ( int i = 0; i < nofColumns; ++i )
452  {
453  whileBlocking( mTableWidget )->item( j, i )->setCheckState( Qt::PartiallyChecked );
454  }
455  }
456  }
457  else if ( mComboBox )
458  {
459  whileBlocking( mComboBox )->setCurrentIndex( -1 );
460  }
461  else if ( mLineEdit )
462  {
463  whileBlocking( mLineEdit )->clear();
464  }
465 }
466 
468 {
469  if ( mEnabled == enabled )
470  return;
471 
472  mEnabled = enabled;
473 
474  if ( mTableWidget )
475  {
476  auto signalBlockedTableWidget = whileBlocking( mTableWidget );
477  Q_UNUSED( signalBlockedTableWidget )
478 
479  for ( int j = 0; j < mTableWidget->rowCount(); j++ )
480  {
481  for ( int i = 0; i < mTableWidget->columnCount(); ++i )
482  {
483  QTableWidgetItem *item = mTableWidget->item( j, i );
484  if ( item )
485  {
486  item->setFlags( enabled ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled );
487  }
488  }
489  }
490  }
491  else
493 }
494 
495 void QgsValueRelationWidgetWrapper::parentFormValueChanged( const QString &attribute, const QVariant &value )
496 {
497 
498  // Update the parent feature in the context ( which means to replace the whole context :/ )
500  QgsFeature feature { context().parentFormFeature() };
501  feature.setAttribute( attribute, value );
502  ctx.setParentFormFeature( feature );
503  setContext( ctx );
504 
505  // Check if the change might affect the filter expression and the cache needs updates
507  && ( config( QStringLiteral( "Value" ) ).toString() == attribute ||
508  config( QStringLiteral( "Key" ) ).toString() == attribute ||
510  QgsValueRelationFieldFormatter::expressionParentFormAttributes( mExpression ).contains( attribute ) ) )
511  {
512  populate();
513  }
514 
515 }
516 
517 void QgsValueRelationWidgetWrapper::emitValueChangedInternal( const QString &value )
518 {
520  emit valueChanged( value );
522  emit valuesChanged( value );
523 }
This class contains context information for attribute editor widgets.
QgsFeature parentFormFeature() const
Returns the feature of the currently edited parent form in its actual state.
void widgetValueChanged(const QString &attribute, const QVariant &value, bool attributeChanged)
Notifies about changes of attributes.
Manages an editor widget Widget and wrapper share the same parent.
QgsFeature formFeature() const
The feature currently being edited, in its current state.
Q_DECL_DEPRECATED void valueChanged(const QVariant &value)
Emit this signal, whenever the value changed.
void setFormFeature(const QgsFeature &feature)
Set the feature currently being edited to feature.
int fieldIdx() const
Access the field index.
void setEnabled(bool enabled) override
Is used to enable or disable the edit functionality of the managed widget.
void valuesChanged(const QVariant &value, const QVariantList &additionalFieldValues=QVariantList())
Emit this signal, whenever the value changed.
bool setFormFeatureAttribute(const QString &attributeName, const QVariant &attributeValue)
Update the feature currently being edited by changing its attribute attributeName to attributeValue.
void emitValueChanged()
Will call the value() method to determine the emitted value.
QgsField field() const
Access the field.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
Definition: qgsfeature.cpp:255
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:320
QVariant::Type type
Definition: qgsfield.h:58
Container of fields for a vector layer.
Definition: qgsfields.h:45
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:344
QStringList names() const
Returns a list with field names.
Definition: qgsfields.cpp:143
QLineEdit subclass with built in support for clearing the widget's value and handling custom null val...
static QString buildArray(const QVariantList &list)
Build a postgres array like formatted list in a string from a QVariantList.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:467
static QgsVectorLayer * resolveLayer(const QVariantMap &config, const QgsProject *project)
Returns the (possibly NULL) layer from the widget's config and project.
static bool expressionRequiresFormScope(const QString &expression)
Check if the expression requires a form scope (i.e.
static bool expressionRequiresParentFormScope(const QString &expression)
Check if the expression requires a parent form scope (i.e.
QVariant createCache(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config) const override
Create a cache for a given field.
static QSet< QString > expressionParentFormVariables(const QString &expression)
Returns a list of variables required by the parent form's form context expression.
static QSet< QString > expressionParentFormAttributes(const QString &expression)
Returns a list of attributes required by the parent form's form context expression.
static QStringList valueToStringList(const QVariant &value)
Utility to convert a list or a string representation of an (hstore style: {1,2...}) list in value to ...
static QSet< QString > expressionFormAttributes(const QString &expression)
Returns a list of attributes required by the form context expression.
QVariant value() const override
Will be used to access the widget's value.
bool valid() const override
Returns true if the widget has been properly initialized.
void showIndeterminateState() override
Sets the widget to display in an indeterminate "mixed value" state.
void parentFormValueChanged(const QString &attribute, const QVariant &value) override
QgsValueRelationWidgetWrapper(QgsVectorLayer *layer, int fieldIdx, QWidget *editor=nullptr, QWidget *parent=nullptr)
Constructor for QgsValueRelationWidgetWrapper.
void setEnabled(bool enabled) override
Is used to enable or disable the edit functionality of the managed widget.
void widgetValueChanged(const QString &attribute, const QVariant &newValue, bool attributeChanged)
Will be called when a value in the current edited form or table row changes.
QWidget * createWidget(QWidget *parent) override
This method should create a new widget with the provided parent.
void setFeature(const QgsFeature &feature) override
Will be called when the feature changes.
void initWidget(QWidget *editor) override
This method should initialize the editor widget with runtime data.
Represents a vector layer which manages a vector based data sets.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
const QgsAttributeEditorContext & context() const
Returns information about the context in which this widget is shown.
QgsVectorLayer * layer() const
Returns the vector layer associated with the widget.
void setContext(const QgsAttributeEditorContext &context)
Set the context in which this widget is shown.
QVariantMap config() const
Returns the whole config.
bool qgsVariantEqual(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether they are equal, two NULL values are always treated a...
Definition: qgis.cpp:274
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:1742
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:1741
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:1185