QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 
29 #include <QHeaderView>
30 #include <QComboBox>
31 #include <QLineEdit>
32 #include <QTableWidget>
33 #include <QStringListModel>
34 #include <QCompleter>
35 
36 QgsValueRelationWidgetWrapper::QgsValueRelationWidgetWrapper( QgsVectorLayer *vl, int fieldIdx, QWidget *editor, QWidget *parent )
37  : QgsEditorWidgetWrapper( vl, fieldIdx, editor, parent )
38 {
39 }
40 
41 
43 {
44  QVariant v;
45 
46  if ( mComboBox )
47  {
48  int cbxIdx = mComboBox->currentIndex();
49  if ( cbxIdx > -1 )
50  {
51  v = mComboBox->currentData();
52  }
53  }
54 
55  const int nofColumns = columnCount();
56 
57  if ( mTableWidget )
58  {
59  QStringList selection;
60  for ( int j = 0; j < mTableWidget->rowCount(); j++ )
61  {
62  for ( int i = 0; i < nofColumns; ++i )
63  {
64  QTableWidgetItem *item = mTableWidget->item( j, i );
65  if ( item )
66  {
67  if ( item->checkState() == Qt::Checked )
68  selection << item->data( Qt::UserRole ).toString();
69  }
70  }
71  }
72  v = selection.join( QStringLiteral( "," ) ).prepend( '{' ).append( '}' );
73  }
74 
75  if ( mLineEdit )
76  {
77  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : qgis::as_const( mCache ) )
78  {
79  if ( item.value == mLineEdit->text() )
80  {
81  v = item.key;
82  break;
83  }
84  }
85  }
86 
87  return v;
88 }
89 
91 {
92  QgsAttributeForm *form = qobject_cast<QgsAttributeForm *>( parent );
93  if ( form )
95 
96  mExpression = config().value( QStringLiteral( "FilterExpression" ) ).toString();
97 
98  if ( config( QStringLiteral( "AllowMulti" ) ).toBool() )
99  {
100  return new QTableWidget( parent );
101  }
102  else if ( config( QStringLiteral( "UseCompleter" ) ).toBool() )
103  {
104  return new QgsFilterLineEdit( parent );
105  }
106  {
107  return new QComboBox( parent );
108  }
109 }
110 
112 {
113 
114  mComboBox = qobject_cast<QComboBox *>( editor );
115  mTableWidget = qobject_cast<QTableWidget *>( editor );
116  mLineEdit = qobject_cast<QLineEdit *>( editor );
117 
118  // Read current initial form values from the editor context
120 
121  if ( mComboBox )
122  {
123  connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
124  this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
125  }
126  else if ( mTableWidget )
127  {
128  mTableWidget->horizontalHeader()->setResizeMode( QHeaderView::Stretch );
129  mTableWidget->horizontalHeader()->setVisible( false );
130  mTableWidget->verticalHeader()->setResizeMode( QHeaderView::Stretch );
131  mTableWidget->verticalHeader()->setVisible( false );
132  mTableWidget->setShowGrid( false );
133  mTableWidget->setEditTriggers( QAbstractItemView::NoEditTriggers );
134  mTableWidget->setSelectionMode( QAbstractItemView::NoSelection );
135  connect( mTableWidget, &QTableWidget::itemChanged, this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
136  }
137  else if ( mLineEdit )
138  {
139  connect( mLineEdit, &QLineEdit::textChanged, this, [ = ]( const QString & value ) { emit valueChanged( value ); }, Qt::UniqueConnection );
140  }
141 }
142 
144 {
145  return mTableWidget || mLineEdit || mComboBox;
146 }
147 
149 {
150  if ( mTableWidget )
151  {
152  QStringList checkList( QgsValueRelationFieldFormatter::valueToStringList( value ) );
153 
154  QTableWidgetItem *lastChangedItem = nullptr;
155 
156  const int nofColumns = columnCount();
157 
158  // This block is needed because item->setCheckState triggers dataChanged gets back to value()
159  // and iterate over all items again! This can be extremely slow on large items sets.
160  for ( int j = 0; j < mTableWidget->rowCount(); j++ )
161  {
162  auto signalBlockedTableWidget = whileBlocking( mTableWidget );
163  Q_UNUSED( signalBlockedTableWidget )
164 
165  for ( int i = 0; i < nofColumns; ++i )
166  {
167  QTableWidgetItem *item = mTableWidget->item( j, i );
168  if ( item )
169  {
170  item->setCheckState( checkList.contains( item->data( Qt::UserRole ).toString() ) ? Qt::Checked : Qt::Unchecked );
171  lastChangedItem = item;
172  }
173  }
174  }
175  // let's trigger the signal now, once and for all
176  if ( lastChangedItem )
177  lastChangedItem->setCheckState( checkList.contains( lastChangedItem->data( Qt::UserRole ).toString() ) ? Qt::Checked : Qt::Unchecked );
178 
179  }
180  else if ( mComboBox )
181  {
182  // findData fails to tell a 0 from a NULL
183  // See: "Value relation, value 0 = NULL" - https://issues.qgis.org/issues/19981
184  int idx = -1; // default to not found
185  for ( int i = 0; i < mComboBox->count(); i++ )
186  {
187  QVariant v( mComboBox->itemData( i ) );
188  if ( qgsVariantEqual( v, value ) )
189  {
190  idx = i;
191  break;
192  }
193  }
194  mComboBox->setCurrentIndex( idx );
195  }
196  else if ( mLineEdit )
197  {
198  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : qgis::as_const( mCache ) )
199  {
200  if ( i.key == value )
201  {
202  mLineEdit->setText( i.value );
203  break;
204  }
205  }
206  }
207 }
208 
209 void QgsValueRelationWidgetWrapper::widgetValueChanged( const QString &attribute, const QVariant &newValue, bool attributeChanged )
210 {
211 
212  // Do nothing if the value has not changed
213  if ( attributeChanged )
214  {
215  QVariant oldValue( value( ) );
216  setFormFeatureAttribute( attribute, newValue );
217  // Update combos if the value used in the filter expression has changed
219  && QgsValueRelationFieldFormatter::expressionFormAttributes( mExpression ).contains( attribute ) )
220  {
221  populate();
222  // Restore value
223  setValue( value( ) );
224  // If the value has changed as a result of another widget's value change,
225  // we need to emit the signal to make sure other dependent widgets are
226  // updated.
227  if ( oldValue != value() && fieldIdx() < formFeature().fields().count() )
228  {
229  QString attributeName( formFeature().fields().names().at( fieldIdx() ) );
230  setFormFeatureAttribute( attributeName, value( ) );
231  emitValueChanged( );
232  }
233  }
234  }
235 }
236 
237 
239 {
240  setFormFeature( feature );
241  whileBlocking( this )->populate();
242  whileBlocking( this )->setValue( feature.attribute( fieldIdx() ) );
243 
244  // As we block any signals, possible depending widgets will not being updated
245  // so we force emit signal once and for all
247 
248  // A bit of logic to set the default value if AllowNull is false and this is a new feature
249  // Note that this needs to be here after the cache has been created/updated by populate()
250  // and signals unblocked (we want this to propagate to the feature itself)
251  if ( formFeature().isValid()
252  && ! formFeature().attribute( fieldIdx() ).isValid()
253  && ! mCache.empty()
254  && ! config( QStringLiteral( "AllowNull" ) ).toBool( ) )
255  {
256  // This is deferred because at the time the feature is set in one widget it is not
257  // set in the next, which is typically the "down" in a drill-down
258  QTimer::singleShot( 0, this, [ this ]
259  {
260  setValue( mCache.at( 0 ).key );
261  } );
262  }
263 }
264 
265 int QgsValueRelationWidgetWrapper::columnCount() const
266 {
267  return std::max( 1, config( QStringLiteral( "NofColumns" ) ).toInt() );
268 }
269 
270 void QgsValueRelationWidgetWrapper::populate( )
271 {
272  // Initialize, note that signals are blocked, to avoid double signals on new features
274  {
276  }
277  else if ( mCache.empty() )
278  {
280  }
281 
282  if ( mComboBox )
283  {
284  mComboBox->clear();
285  if ( config( QStringLiteral( "AllowNull" ) ).toBool( ) )
286  {
287  whileBlocking( mComboBox )->addItem( tr( "(no selection)" ), QVariant( field().type( ) ) );
288  }
289 
290  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : qgis::as_const( mCache ) )
291  {
292  whileBlocking( mComboBox )->addItem( element.value, element.key );
293  }
294  }
295  else if ( mTableWidget )
296  {
297  const int nofColumns = columnCount();
298 
299  if ( ! mCache.empty() )
300  {
301  mTableWidget->setRowCount( ( mCache.size() + nofColumns - 1 ) / nofColumns );
302  }
303  else
304  mTableWidget->setRowCount( 1 );
305  mTableWidget->setColumnCount( nofColumns );
306 
307  whileBlocking( mTableWidget )->clear();
308  int row = 0;
309  int column = 0;
310  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : qgis::as_const( mCache ) )
311  {
312  if ( column == nofColumns )
313  {
314  row++;
315  column = 0;
316  }
317  QTableWidgetItem *item = nullptr;
318  item = new QTableWidgetItem( element.value );
319  item->setData( Qt::UserRole, element.key );
320  whileBlocking( mTableWidget )->setItem( row, column, item );
321  column++;
322  }
323  }
324  else if ( mLineEdit )
325  {
326  QStringList values;
327  values.reserve( mCache.size() );
328  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : qgis::as_const( mCache ) )
329  {
330  values << i.value;
331  }
332  QStringListModel *m = new QStringListModel( values, mLineEdit );
333  QCompleter *completer = new QCompleter( m, mLineEdit );
334  completer->setCaseSensitivity( Qt::CaseInsensitive );
335  mLineEdit->setCompleter( completer );
336  }
337 }
338 
340 {
341  const int nofColumns = columnCount();
342 
343  if ( mTableWidget )
344  {
345  for ( int j = 0; j < mTableWidget->rowCount(); j++ )
346  {
347  for ( int i = 0; i < nofColumns; ++i )
348  {
349  whileBlocking( mTableWidget )->item( j, i )->setCheckState( Qt::PartiallyChecked );
350  }
351  }
352  }
353  else if ( mComboBox )
354  {
355  whileBlocking( mComboBox )->setCurrentIndex( -1 );
356  }
357  else if ( mLineEdit )
358  {
359  whileBlocking( mLineEdit )->clear();
360  }
361 }
362 
364 {
365  if ( mEnabled == enabled )
366  return;
367 
368  mEnabled = enabled;
369 
370  if ( mTableWidget )
371  {
372  auto signalBlockedTableWidget = whileBlocking( mTableWidget );
373  Q_UNUSED( signalBlockedTableWidget )
374 
375  for ( int j = 0; j < mTableWidget->rowCount(); j++ )
376  {
377  for ( int i = 0; i < mTableWidget->columnCount(); ++i )
378  {
379  QTableWidgetItem *item = mTableWidget->item( j, i );
380  if ( item )
381  {
382  if ( enabled )
383  item->setFlags( item->flags() | Qt::ItemIsEnabled );
384  else
385  item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
386  }
387  }
388  }
389  }
390  else
392 }
void emitValueChanged()
Will call the value() method to determine the emitted value.
int fieldIdx() const
Access the field index.
QVariantMap config() const
Returns the whole config.
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.
QgsField field() const
Access the field.
void setFormFeature(const QgsFeature &feature)
Set the feature currently being edited to feature.
void setValue(const QVariant &value) override
void setEnabled(bool enabled) override
Is used to enable or disable the edit functionality of the managed widget.
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.
const QgsAttributeEditorContext & context() const
Returns information about the context in which this widget is shown.
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...
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 QSet< QString > expressionFormAttributes(const QString &expression)
Returns a list of attributes required by the form context expression.
void setEnabled(bool enabled) override
Is used to enable or disable the edit functionality of the managed widget.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:225
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
void valueChanged(const QVariant &value)
Emit this signal, whenever the value changed.
static QStringList valueToStringList(const QVariant &value)
Utility to convert an array or a string representation of an array value to a string list...
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.
bool qgsVariantEqual(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether they are equal, NULL values are treated as equal...
Definition: qgis.cpp:310
Represents a vector layer which manages a vector based data sets.
QgsFeature formFeature() const
The feature currently being edited, in its current state.
QVariant value() const override
Will be used to access the widget&#39;s value.
QgsValueRelationWidgetWrapper(QgsVectorLayer *vl, int fieldIdx, QWidget *editor=nullptr, QWidget *parent=nullptr)