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