QGIS API Documentation  3.2.0-Bonn (bc43194)
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  mComboBox->setCurrentIndex( mComboBox->findData( value ) );
183  }
184  else if ( mLineEdit )
185  {
186  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : qgis::as_const( mCache ) )
187  {
188  if ( i.key == value )
189  {
190  mLineEdit->setText( i.value );
191  break;
192  }
193  }
194  }
195 }
196 
197 void QgsValueRelationWidgetWrapper::widgetValueChanged( const QString &attribute, const QVariant &newValue, bool attributeChanged )
198 {
199 
200  // Do nothing if the value has not changed
201  if ( attributeChanged )
202  {
203  setFormFeatureAttribute( attribute, newValue );
204  // Update combos if the value used in the filter expression has changed
206  && QgsValueRelationFieldFormatter::expressionFormAttributes( mExpression ).contains( attribute ) )
207  {
208  populate();
209  // Restore value
210  setValue( value( ) );
211  }
212  }
213 }
214 
215 
217 {
218  setFormFeature( feature );
219  whileBlocking( this )->populate();
220  whileBlocking( this )->setValue( feature.attribute( fieldIdx() ) );
221  // A bit of logic to set the default value if AllowNull is false and this is a new feature
222  // Note that this needs to be here after the cache has been created/updated by populate()
223  // and signals unblocked (we want this to propagate to the feature itself)
224  if ( formFeature().isValid()
225  && ! formFeature().attribute( fieldIdx() ).isValid()
226  && ! mCache.empty()
227  && ! config( QStringLiteral( "AllowNull" ) ).toBool( ) )
228  {
229  // This is deferred because at the time the feature is set in one widget it is not
230  // set in the next, which is typically the "down" in a drill-down
231  QTimer::singleShot( 0, [ this ]
232  {
233  setValue( mCache.at( 0 ).key );
234  } );
235  }
236 }
237 
238 int QgsValueRelationWidgetWrapper::columnCount() const
239 {
240  return std::max( 1, config( QStringLiteral( "NofColumns" ) ).toInt() );
241 }
242 
243 void QgsValueRelationWidgetWrapper::populate( )
244 {
245  // Initialize, note that signals are blocked, to avoid double signals on new features
247  {
249  }
250  else if ( mCache.empty() )
251  {
253  }
254 
255  if ( mComboBox )
256  {
257  mComboBox->clear();
258  if ( config( QStringLiteral( "AllowNull" ) ).toBool( ) )
259  {
260  whileBlocking( mComboBox )->addItem( tr( "(no selection)" ), QVariant( field().type( ) ) );
261  }
262 
263  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : qgis::as_const( mCache ) )
264  {
265  whileBlocking( mComboBox )->addItem( element.value, element.key );
266  }
267  }
268  else if ( mTableWidget )
269  {
270  const int nofColumns = columnCount();
271 
272  if ( ! mCache.empty() )
273  {
274  mTableWidget->setRowCount( ( mCache.size() + nofColumns - 1 ) / nofColumns );
275  }
276  else
277  mTableWidget->setRowCount( 1 );
278  mTableWidget->setColumnCount( nofColumns );
279 
280  whileBlocking( mTableWidget )->clear();
281  int row = 0;
282  int column = 0;
283  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : qgis::as_const( mCache ) )
284  {
285  if ( column == nofColumns )
286  {
287  row++;
288  column = 0;
289  }
290  QTableWidgetItem *item = nullptr;
291  item = new QTableWidgetItem( element.value );
292  item->setData( Qt::UserRole, element.key );
293  whileBlocking( mTableWidget )->setItem( row, column, item );
294  column++;
295  }
296  }
297  else if ( mLineEdit )
298  {
299  QStringList values;
300  values.reserve( mCache.size() );
301  for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : qgis::as_const( mCache ) )
302  {
303  values << i.value;
304  }
305  QStringListModel *m = new QStringListModel( values, mLineEdit );
306  QCompleter *completer = new QCompleter( m, mLineEdit );
307  completer->setCaseSensitivity( Qt::CaseInsensitive );
308  mLineEdit->setCompleter( completer );
309  }
310 }
311 
313 {
314  const int nofColumns = columnCount();
315 
316  if ( mTableWidget )
317  {
318  for ( int j = 0; j < mTableWidget->rowCount(); j++ )
319  {
320  for ( int i = 0; i < nofColumns; ++i )
321  {
322  whileBlocking( mTableWidget )->item( j, i )->setCheckState( Qt::PartiallyChecked );
323  }
324  }
325  }
326  else if ( mComboBox )
327  {
328  whileBlocking( mComboBox )->setCurrentIndex( -1 );
329  }
330  else if ( mLineEdit )
331  {
332  whileBlocking( mLineEdit )->clear();
333  }
334 }
335 
337 {
338  if ( mEnabled == enabled )
339  return;
340 
341  mEnabled = enabled;
342 
343  if ( mTableWidget )
344  {
345  auto signalBlockedTableWidget = whileBlocking( mTableWidget );
346  Q_UNUSED( signalBlockedTableWidget )
347 
348  for ( int j = 0; j < mTableWidget->rowCount(); j++ )
349  {
350  for ( int i = 0; i < mTableWidget->columnCount(); ++i )
351  {
352  QTableWidgetItem *item = mTableWidget->item( j, i );
353  if ( item )
354  {
355  if ( enabled )
356  item->setFlags( item->flags() | Qt::ItemIsEnabled );
357  else
358  item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
359  }
360  }
361  }
362  }
363  else
365 }
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:176
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.
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.
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:62
bool valid() const override
Returns true if the widget has been properly initialized.
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...
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 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.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:224
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.
int fieldIdx() const
Access the field index.
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:255
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)