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