QGIS API Documentation 3.43.0-Master (56aa1fd18d7)
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#include "moc_qgsvaluerelationwidgetwrapper.cpp"
18
19#include "qgis.h"
20#include "qgsfields.h"
21#include "qgsproject.h"
22#include "qgsvectorlayer.h"
23#include "qgsfilterlineedit.h"
25#include "qgsattributeform.h"
27
28#include <QComboBox>
29#include <QLineEdit>
30#include <QStringListModel>
31#include <QCompleter>
32#include <QTimer>
33#include <QVBoxLayout>
34#include <QHeaderView>
35#include <QKeyEvent>
36#include <QStandardItemModel>
37#include <QMenu>
38
39#include <nlohmann/json.hpp>
40using namespace nlohmann;
41
43QgsFilteredTableWidget::QgsFilteredTableWidget( QWidget *parent, bool showSearch, bool displayGroupName )
44 : QWidget( parent )
45 , mDisplayGroupName( displayGroupName )
46{
47 mSearchWidget = new QgsFilterLineEdit( this );
48 mSearchWidget->setShowSearchIcon( true );
49 mSearchWidget->setShowClearButton( true );
50 mTableWidget = new QTableWidget( this );
51 mTableWidget->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );
52 mTableWidget->horizontalHeader()->setVisible( false );
53 mTableWidget->verticalHeader()->setSectionResizeMode( QHeaderView::Stretch );
54 mTableWidget->verticalHeader()->setVisible( false );
55 mTableWidget->setShowGrid( false );
56 mTableWidget->setEditTriggers( QAbstractItemView::NoEditTriggers );
57 mTableWidget->setSelectionMode( QAbstractItemView::NoSelection );
58 mTableWidget->setContextMenuPolicy( Qt::CustomContextMenu );
59
60 QVBoxLayout *layout = new QVBoxLayout();
61 layout->addWidget( mSearchWidget );
62 layout->addWidget( mTableWidget );
63 layout->setContentsMargins( 0, 0, 0, 0 );
64 layout->setSpacing( 0 );
65 if ( showSearch )
66 {
67 mTableWidget->setFocusProxy( mSearchWidget );
68 connect( mSearchWidget, &QgsFilterLineEdit::textChanged, this, &QgsFilteredTableWidget::filterStringChanged );
69 installEventFilter( this );
70 }
71 else
72 {
73 mSearchWidget->setVisible( false );
74 }
75 setLayout( layout );
76 connect( mTableWidget, &QTableWidget::itemChanged, this, &QgsFilteredTableWidget::itemChanged_p );
77 connect( mTableWidget, &QTableWidget::customContextMenuRequested, this, &QgsFilteredTableWidget::onTableWidgetCustomContextMenuRequested );
78}
79
80bool QgsFilteredTableWidget::eventFilter( QObject *watched, QEvent *event )
81{
82 Q_UNUSED( watched )
83 if ( event->type() == QEvent::KeyPress )
84 {
85 QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
86 if ( keyEvent->key() == Qt::Key_Escape && !mSearchWidget->text().isEmpty() )
87 {
88 mSearchWidget->clear();
89 return true;
90 }
91 }
92 return false;
93}
94
95void QgsFilteredTableWidget::filterStringChanged( const QString &filterString )
96{
97 auto signalBlockedTableWidget = whileBlocking( mTableWidget );
98 Q_UNUSED( signalBlockedTableWidget )
99
100 mTableWidget->clearContents();
101 if ( !mCache.isEmpty() )
102 {
103 QVariantList groups;
104 groups << QVariant();
105 for ( const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
106 {
107 if ( !groups.contains( pair.first.group ) )
108 {
109 groups << pair.first.group;
110 }
111 }
112 const int groupsCount = mDisplayGroupName ? groups.count() : groups.count() - 1;
113
114 const int rCount = std::max( 1, ( int ) std::ceil( ( float ) ( mCache.count() + groupsCount ) / ( float ) mColumnCount ) );
115 mTableWidget->setRowCount( rCount );
116
117 int row = 0;
118 int column = 0;
119 QVariant currentGroup;
120 for ( const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
121 {
122 if ( column == mColumnCount )
123 {
124 row++;
125 column = 0;
126 }
127 if ( currentGroup != pair.first.group )
128 {
129 currentGroup = pair.first.group;
130 if ( mDisplayGroupName || !( row == 0 && column == 0 ) )
131 {
132 QTableWidgetItem *item = new QTableWidgetItem( mDisplayGroupName ? pair.first.group.toString() : QString() );
133 item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
134 mTableWidget->setItem( row, column, item );
135 column++;
136 if ( column == mColumnCount )
137 {
138 row++;
139 column = 0;
140 }
141 }
142 }
143 if ( pair.first.value.contains( filterString, Qt::CaseInsensitive ) )
144 {
145 QTableWidgetItem *item = new QTableWidgetItem( pair.first.value );
146 item->setData( Qt::UserRole, pair.first.key );
147 item->setData( Qt::ToolTipRole, pair.first.description );
148 item->setCheckState( pair.second );
149 item->setFlags( mEnabledTable ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled );
150 mTableWidget->setItem( row, column, item );
151 column++;
152 }
153 }
154 mTableWidget->setRowCount( row + 1 );
155 }
156}
157
158QStringList QgsFilteredTableWidget::selection() const
159{
160 QStringList sel;
161 for ( const QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : std::as_const( mCache ) )
162 {
163 if ( pair.second == Qt::Checked )
164 sel.append( pair.first.key.toString() );
165 }
166 return sel;
167}
168
169void QgsFilteredTableWidget::checkItems( const QStringList &checked )
170{
171 for ( QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : mCache )
172 {
173 const bool isChecked = checked.contains( pair.first.key.toString() );
174 pair.second = isChecked ? Qt::Checked : Qt::Unchecked;
175 }
176
177 filterStringChanged( mSearchWidget->text() );
178}
179
180void QgsFilteredTableWidget::populate( QgsValueRelationFieldFormatter::ValueRelationCache cache )
181{
182 mCache.clear();
183 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : std::as_const( cache ) )
184 {
185 mCache.append( qMakePair( element, Qt::Unchecked ) );
186 }
187 filterStringChanged( mSearchWidget->text() );
188}
189
190void QgsFilteredTableWidget::setIndeterminateState()
191{
192 for ( int rowIndex = 0; rowIndex < mTableWidget->rowCount(); rowIndex++ )
193 {
194 for ( int columnIndex = 0; columnIndex < mColumnCount; ++columnIndex )
195 {
196 if ( item( rowIndex, columnIndex ) )
197 {
198 whileBlocking( mTableWidget )->item( rowIndex, columnIndex )->setCheckState( Qt::PartiallyChecked );
199 }
200 else
201 {
202 break;
203 }
204 }
205 }
206}
207
208void QgsFilteredTableWidget::setEnabledTable( const bool enabled )
209{
210 if ( mEnabledTable == enabled )
211 return;
212
213 mEnabledTable = enabled;
214 if ( !enabled )
215 mSearchWidget->clear();
216
217 filterStringChanged( mSearchWidget->text() );
218}
219
220void QgsFilteredTableWidget::setColumnCount( const int count )
221{
222 mColumnCount = count;
223 mTableWidget->setColumnCount( count );
224}
225
226void QgsFilteredTableWidget::itemChanged_p( QTableWidgetItem *item )
227{
228 for ( QPair<QgsValueRelationFieldFormatter::ValueRelationItem, Qt::CheckState> &pair : mCache )
229 {
230 if ( pair.first.key == item->data( Qt::UserRole ) )
231 pair.second = item->checkState();
232 }
233 emit itemChanged( item );
234}
235
236void QgsFilteredTableWidget::onTableWidgetCustomContextMenuRequested( const QPoint &pos )
237{
238 Q_UNUSED( pos );
239
240 // create actions
241 QAction *actionTableWidgetSelectAll = new QAction( tr( "Select All" ), this );
242 QAction *actionTableWidgetDeselectAll = new QAction( tr( "Deselect All" ), this );
243 connect( actionTableWidgetSelectAll, &QAction::triggered, this, &QgsFilteredTableWidget::onTableWidgetMenuActionSelectAllTriggered );
244 connect( actionTableWidgetDeselectAll, &QAction::triggered, this, &QgsFilteredTableWidget::onTableWidgetMenuActionDeselectAllTriggered );
245
246 QMenu *tableWidgetMenu = new QMenu( mTableWidget );
247 tableWidgetMenu->addAction( actionTableWidgetSelectAll );
248 tableWidgetMenu->addAction( actionTableWidgetDeselectAll );
249
250 tableWidgetMenu->exec( QCursor::pos() );
251
252 // destory actions
253 disconnect( actionTableWidgetSelectAll, &QAction::triggered, nullptr, nullptr );
254 disconnect( actionTableWidgetDeselectAll, &QAction::triggered, nullptr, nullptr );
255 actionTableWidgetSelectAll->deleteLater();
256 actionTableWidgetDeselectAll->deleteLater();
257
258 // destory menu
259 tableWidgetMenu->deleteLater();
260}
261
262void QgsFilteredTableWidget::onTableWidgetMenuActionSelectAllTriggered()
263{
264 for ( int rowIndex = 0; rowIndex < mTableWidget->rowCount(); ++rowIndex )
265 {
266 for ( int columnIndex = 0; columnIndex < mTableWidget->columnCount(); ++columnIndex )
267 {
268 QTableWidgetItem *item = whileBlocking( mTableWidget )->item( rowIndex, columnIndex );
269 if ( item && ( item->flags() & Qt::ItemIsEnabled ) )
270 {
271 item->setCheckState( Qt::Checked );
272 }
273 }
274 }
275}
276
277void QgsFilteredTableWidget::onTableWidgetMenuActionDeselectAllTriggered()
278{
279 for ( int rowIndex = 0; rowIndex < mTableWidget->rowCount(); ++rowIndex )
280 {
281 for ( int columnIndex = 0; columnIndex < mTableWidget->columnCount(); ++columnIndex )
282 {
283 QTableWidgetItem *item = whileBlocking( mTableWidget )->item( rowIndex, columnIndex );
284 if ( item && ( item->flags() & Qt::ItemIsEnabled ) )
285 {
286 item->setCheckState( Qt::Unchecked );
287 }
288 }
289 }
290}
292
293
294QgsValueRelationWidgetWrapper::QgsValueRelationWidgetWrapper( QgsVectorLayer *layer, int fieldIdx, QWidget *editor, QWidget *parent )
295 : QgsEditorWidgetWrapper( layer, fieldIdx, editor, parent )
296{
297}
298
300{
301 QVariant v;
302
303 if ( mComboBox )
304 {
305 int cbxIdx = mComboBox->currentIndex();
306 if ( cbxIdx > -1 )
307 {
308 v = mComboBox->currentData();
309 if ( QgsVariantUtils::isNull( v ) )
311 }
312 }
313 else if ( mTableWidget )
314 {
315 QStringList selection = mTableWidget->selection();
316
317 // If there is no selection and allow NULL is not checked return NULL.
318 if ( selection.isEmpty() && !config( QStringLiteral( "AllowNull" ) ).toBool() )
319 {
320 return QgsVariantUtils::createNullVariant( QMetaType::Type::QVariantList );
321 }
322
323 QVariantList vl;
324 //store as QVariantList because the field type supports data structure
325 for ( const QString &s : std::as_const( selection ) )
326 {
327 // Convert to proper type
328 const QMetaType::Type type { fkType() };
329 switch ( type )
330 {
331 case QMetaType::Type::Int:
332 vl.push_back( s.toInt() );
333 break;
334 case QMetaType::Type::LongLong:
335 vl.push_back( s.toLongLong() );
336 break;
337 default:
338 vl.push_back( s );
339 break;
340 }
341 }
342
343 if ( layer()->fields().at( fieldIdx() ).type() == QMetaType::Type::QVariantMap || layer()->fields().at( fieldIdx() ).type() == QMetaType::Type::QVariantList )
344 {
345 v = vl;
346 }
347 else
348 {
349 //make string
351 }
352 }
353 else if ( mLineEdit )
354 {
355 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : std::as_const( mCache ) )
356 {
357 if ( item.value == mLineEdit->text() )
358 {
359 v = item.key;
360 break;
361 }
362 }
363 }
364
365 return v;
366}
367
369{
370 QgsAttributeForm *form = qobject_cast<QgsAttributeForm *>( parent );
371 if ( form )
373
374 mExpression = config().value( QStringLiteral( "FilterExpression" ) ).toString();
375
376 const bool allowMulti = config( QStringLiteral( "AllowMulti" ) ).toBool();
377 const bool useCompleter = config( QStringLiteral( "UseCompleter" ) ).toBool();
378 if ( allowMulti )
379 {
380 const bool displayGroupName = config( QStringLiteral( "DisplayGroupName" ) ).toBool();
381 return new QgsFilteredTableWidget( parent, useCompleter, displayGroupName );
382 }
383 else if ( useCompleter )
384 {
385 return new QgsFilterLineEdit( parent );
386 }
387 else
388 {
389 QgsToolTipComboBox *combo = new QgsToolTipComboBox( parent );
390 combo->setMinimumContentsLength( 1 );
391 combo->setSizeAdjustPolicy( QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon );
392 return combo;
393 }
394}
395
397{
398 mComboBox = qobject_cast<QComboBox *>( editor );
399 mTableWidget = qobject_cast<QgsFilteredTableWidget *>( editor );
400 mLineEdit = qobject_cast<QLineEdit *>( editor );
401
402 // Read current initial form values from the editor context
404
405 if ( mComboBox )
406 {
407 mComboBox->view()->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
408 connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
409 }
410 else if ( mTableWidget )
411 {
412 connect( mTableWidget, &QgsFilteredTableWidget::itemChanged, this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
413 }
414 else if ( mLineEdit )
415 {
416 if ( QgsFilterLineEdit *filterLineEdit = qobject_cast<QgsFilterLineEdit *>( editor ) )
417 {
418 connect( filterLineEdit, &QgsFilterLineEdit::valueChanged, this, [=]( const QString & ) {
419 if ( mSubWidgetSignalBlocking == 0 )
421 } );
422 }
423 else
424 {
425 connect( mLineEdit, &QLineEdit::textChanged, this, &QgsValueRelationWidgetWrapper::emitValueChangedInternal, Qt::UniqueConnection );
426 }
427 }
428}
429
431{
432 return mTableWidget || mLineEdit || mComboBox;
433}
434
435void QgsValueRelationWidgetWrapper::updateValues( const QVariant &value, const QVariantList & )
436{
437 updateValue( value, true );
438}
439
440void QgsValueRelationWidgetWrapper::updateValue( const QVariant &value, bool forceComboInsertion )
441{
442 if ( mTableWidget )
443 {
444 QStringList checkList;
445
446 if ( layer()->fields().at( fieldIdx() ).type() == QMetaType::Type::QVariantMap || layer()->fields().at( fieldIdx() ).type() == QMetaType::Type::QVariantList )
447 {
448 checkList = value.toStringList();
449 }
450 else
451 {
453 }
454
455 mTableWidget->checkItems( checkList );
456 }
457 else if ( mComboBox )
458 {
459 // findData fails to tell a 0 from a NULL
460 // See: "Value relation, value 0 = NULL" - https://github.com/qgis/QGIS/issues/27803
461 int idx = -1; // default to not found
462 for ( int i = 0; i < mComboBox->count(); i++ )
463 {
464 QVariant v( mComboBox->itemData( i ) );
465 if ( qgsVariantEqual( v, value ) )
466 {
467 idx = i;
468 break;
469 }
470 }
471
472 if ( idx == -1 )
473 {
474 // if value doesn't exist, we show it in '(...)' (just like value map widget)
475 if ( QgsVariantUtils::isNull( value ) || !forceComboInsertion )
476 {
477 mComboBox->setCurrentIndex( -1 );
478 }
479 else
480 {
481 mComboBox->addItem( value.toString().prepend( '(' ).append( ')' ), value );
482 mComboBox->setCurrentIndex( mComboBox->findData( value ) );
483 }
484 }
485 else
486 {
487 mComboBox->setCurrentIndex( idx );
488 }
489 }
490 else if ( mLineEdit )
491 {
492 mSubWidgetSignalBlocking++;
493 mLineEdit->clear();
494 bool wasFound { false };
495 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : std::as_const( mCache ) )
496 {
497 if ( i.key == value )
498 {
499 mLineEdit->setText( i.value );
500 wasFound = true;
501 break;
502 }
503 }
504 // Value could not be found
505 if ( !wasFound )
506 {
507 mLineEdit->setText( tr( "(no selection)" ) );
508 }
509 mSubWidgetSignalBlocking--;
510 }
511}
512
513void QgsValueRelationWidgetWrapper::widgetValueChanged( const QString &attribute, const QVariant &newValue, bool attributeChanged )
514{
515 // Do nothing if the value has not changed except for multi edit mode
516 // In multi edit mode feature is not updated (so attributeChanged is false) until user validate it but we need to update the
517 // value relation which could have an expression depending on another modified field
518 if ( attributeChanged || context().attributeFormMode() == QgsAttributeEditorContext::Mode::MultiEditMode )
519 {
520 QVariant oldValue( value() );
521 setFormFeatureAttribute( attribute, newValue );
522 // Update combos if the value used in the filter expression has changed
524 && QgsValueRelationFieldFormatter::expressionFormAttributes( mExpression ).contains( attribute ) )
525 {
526 populate();
527 // Restore value
528 updateValue( oldValue, false );
529 // If the value has changed as a result of another widget's value change,
530 // we need to emit the signal to make sure other dependent widgets are
531 // updated.
532 QgsFields formFields( formFeature().fields() );
533
534 // Also check for fields in the layer in case this is a multi-edit form
535 // and there is not form feature set
536 if ( formFields.count() == 0 && layer() )
537 {
538 formFields = layer()->fields();
539 }
540
541 if ( oldValue != value() && fieldIdx() < formFields.count() )
542 {
543 QString attributeName( formFields.names().at( fieldIdx() ) );
544 setFormFeatureAttribute( attributeName, value() );
546 }
547 }
548 }
549}
550
551
553{
554 setFormFeature( feature );
555 whileBlocking( this )->populate();
556 whileBlocking( this )->setValue( feature.attribute( fieldIdx() ) );
557
558 // As we block any signals, possible depending widgets will not being updated
559 // so we force emit signal once and for all
561
562 // A bit of logic to set the default value if AllowNull is false and this is a new feature
563 // Note that this needs to be here after the cache has been created/updated by populate()
564 // and signals unblocked (we want this to propagate to the feature itself)
565 if ( context().attributeFormMode() != QgsAttributeEditorContext::Mode::MultiEditMode
566 && !formFeature().attribute( fieldIdx() ).isValid()
567 && !mCache.isEmpty()
568 && !config( QStringLiteral( "AllowNull" ) ).toBool() )
569 {
570 // This is deferred because at the time the feature is set in one widget it is not
571 // set in the next, which is typically the "down" in a drill-down
572 QTimer::singleShot( 0, this, [this] {
573 if ( !mCache.isEmpty() )
574 {
575 updateValues( formFeature().attribute( fieldIdx() ).isValid() ? formFeature().attribute( fieldIdx() ) : mCache.at( 0 ).key );
576 }
577 } );
578 }
579}
580
581int QgsValueRelationWidgetWrapper::columnCount() const
582{
583 return std::max( 1, config( QStringLiteral( "NofColumns" ) ).toInt() );
584}
585
586
587QMetaType::Type QgsValueRelationWidgetWrapper::fkType() const
588{
590 if ( layer )
591 {
592 QgsFields fields = layer->fields();
593 int idx { fields.lookupField( config().value( QStringLiteral( "Key" ) ).toString() ) };
594 if ( idx >= 0 )
595 {
596 return fields.at( idx ).type();
597 }
598 }
599 return QMetaType::Type::UnknownType;
600}
601
602void QgsValueRelationWidgetWrapper::populate()
603{
604 // Initialize, note that signals are blocked, to avoid double signals on new features
606 {
607 if ( context().parentFormFeature().isValid() )
608 {
609 mCache = QgsValueRelationFieldFormatter::createCache( config(), formFeature(), context().parentFormFeature() );
610 }
611 else
612 {
614 }
615 }
616 else if ( mCache.empty() )
617 {
619 }
620
621 if ( mComboBox )
622 {
623 mComboBox->blockSignals( true );
624 mComboBox->clear();
625 const bool allowNull = config( QStringLiteral( "AllowNull" ) ).toBool();
626 if ( allowNull )
627 {
628 mComboBox->addItem( tr( "(no selection)" ), QgsVariantUtils::createNullVariant( field().type() ) );
629 }
630
631 if ( !mCache.isEmpty() )
632 {
633 QVariant currentGroup;
634 QStandardItemModel *model = qobject_cast<QStandardItemModel *>( mComboBox->model() );
635 const bool displayGroupName = config( QStringLiteral( "DisplayGroupName" ) ).toBool();
636 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : std::as_const( mCache ) )
637 {
638 if ( currentGroup != element.group )
639 {
640 if ( mComboBox->count() > ( allowNull ? 1 : 0 ) )
641 {
642 mComboBox->insertSeparator( mComboBox->count() );
643 }
644 if ( displayGroupName )
645 {
646 mComboBox->addItem( element.group.toString() );
647 QStandardItem *item = model->item( mComboBox->count() - 1 );
648 item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
649 }
650 currentGroup = element.group;
651 }
652
653 mComboBox->addItem( element.value, element.key );
654
655 if ( !element.description.isEmpty() )
656 {
657 mComboBox->setItemData( mComboBox->count() - 1, element.description, Qt::ToolTipRole );
658 }
659 }
660 }
661 mComboBox->blockSignals( false );
662 }
663 else if ( mTableWidget )
664 {
665 mTableWidget->setColumnCount( columnCount() );
666 mTableWidget->populate( mCache );
667 }
668 else if ( mLineEdit )
669 {
670 QStringList values;
671 values.reserve( mCache.size() );
672 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : std::as_const( mCache ) )
673 {
674 values << i.value;
675 }
676 QStringListModel *m = new QStringListModel( values, mLineEdit );
677 QCompleter *completer = new QCompleter( m, mLineEdit );
678
679 const Qt::MatchFlags completerMatchFlags { config().contains( QStringLiteral( "CompleterMatchFlags" ) ) ? static_cast<Qt::MatchFlags>( config().value( QStringLiteral( "CompleterMatchFlags" ), Qt::MatchFlag::MatchStartsWith ).toInt() ) : Qt::MatchFlag::MatchStartsWith };
680
681 if ( completerMatchFlags.testFlag( Qt::MatchFlag::MatchContains ) )
682 {
683 completer->setFilterMode( Qt::MatchFlag::MatchContains );
684 }
685 else
686 {
687 completer->setFilterMode( Qt::MatchFlag::MatchStartsWith );
688 }
689 completer->setCaseSensitivity( Qt::CaseInsensitive );
690 mLineEdit->setCompleter( completer );
691 }
692}
693
695{
696 if ( mTableWidget )
697 {
698 mTableWidget->setIndeterminateState();
699 }
700 else if ( mComboBox )
701 {
702 whileBlocking( mComboBox )->setCurrentIndex( -1 );
703 }
704 else if ( mLineEdit )
705 {
706 whileBlocking( mLineEdit )->clear();
707 }
708}
709
711{
712 if ( mEnabled == enabled )
713 return;
714
715 mEnabled = enabled;
716
717 if ( mTableWidget )
718 {
719 mTableWidget->setEnabledTable( enabled );
720 }
721 else
723}
724
725void QgsValueRelationWidgetWrapper::parentFormValueChanged( const QString &attribute, const QVariant &value )
726{
727 // Update the parent feature in the context ( which means to replace the whole context :/ )
729 QgsFeature feature { context().parentFormFeature() };
730 feature.setAttribute( attribute, value );
731 ctx.setParentFormFeature( feature );
732 setContext( ctx );
733
734 // Check if the change might affect the filter expression and the cache needs updates
736 && ( config( QStringLiteral( "Value" ) ).toString() == attribute || config( QStringLiteral( "Key" ) ).toString() == attribute || !QgsValueRelationFieldFormatter::expressionParentFormVariables( mExpression ).isEmpty() || QgsValueRelationFieldFormatter::expressionParentFormAttributes( mExpression ).contains( attribute ) ) )
737 {
738 populate();
739 }
740}
741
742void QgsValueRelationWidgetWrapper::emitValueChangedInternal( const QString &value )
743{
745 emit valueChanged( value );
747 emit valuesChanged( value );
748}
Contains context information for attribute editor widgets.
QgsFeature parentFormFeature() const
Returns the feature of the currently edited parent form in its actual state.
@ MultiEditMode
Multi edit mode, for editing fields of multiple features at once.
The attribute form widget for vector layer features.
void widgetValueChanged(const QString &attribute, const QVariant &value, bool attributeChanged)
Notifies about changes of attributes.
Manages an editor widget.
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:58
Q_INVOKABLE bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
QMetaType::Type type
Definition qgsfield.h:60
Container of fields for a vector layer.
Definition qgsfields.h:46
int count
Definition qgsfields.h:50
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
QStringList names
Definition qgsfields.h:51
QLineEdit subclass with built in support for clearing the widget's value and handling custom null val...
void valueChanged(const QString &value)
Same as textChanged() but with support for null values.
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.
QComboBox subclass which features a tooltip when mouse hovering the combobox.
static QgsVectorLayer * resolveLayer(const QVariantMap &config, const QgsProject *project)
Returns the (possibly nullptr) 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.
QVector< QgsValueRelationFieldFormatter::ValueRelationItem > ValueRelationCache
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.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
static QVariant createNullVariant(QMetaType::Type metaType)
Helper method to properly create a null QVariant from a metaType Returns the created QVariant.
Represents a vector layer which manages a vector based dataset.
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:351
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6855
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6854
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6206