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