QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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
302
304{
305 QVariant v;
306
307 if ( mComboBox )
308 {
309 int cbxIdx = mComboBox->currentIndex();
310 if ( cbxIdx > -1 )
311 {
312 v = mComboBox->currentData();
313 if ( QgsVariantUtils::isNull( v ) )
315 }
316 }
317 else if ( mTableWidget )
318 {
319 QStringList selection = mTableWidget->selection();
320
321 // If there is no selection and allow NULL is not checked return NULL.
322 if ( selection.isEmpty() && !config( u"AllowNull"_s ).toBool() )
323 {
324 return QgsVariantUtils::createNullVariant( QMetaType::Type::QVariantList );
325 }
326
327 QVariantList vl;
328 //store as QVariantList because the field type supports data structure
329 for ( const QString &s : std::as_const( selection ) )
330 {
331 // Convert to proper type
332 const QMetaType::Type type { fkType() };
333 switch ( type )
334 {
335 case QMetaType::Type::Int:
336 vl.push_back( s.toInt() );
337 break;
338 case QMetaType::Type::LongLong:
339 vl.push_back( s.toLongLong() );
340 break;
341 default:
342 vl.push_back( s );
343 break;
344 }
345 }
346
347 if ( layer()->fields().at( fieldIdx() ).type() == QMetaType::Type::QVariantMap || layer()->fields().at( fieldIdx() ).type() == QMetaType::Type::QVariantList )
348 {
349 v = vl;
350 }
351 else
352 {
353 //make string
355 }
356 }
357 else if ( mLineEdit )
358 {
359 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &item : std::as_const( mCache ) )
360 {
361 if ( item.value == mLineEdit->text() )
362 {
363 v = item.key;
364 break;
365 }
366 }
367 }
368
369 return v;
370}
371
373{
374 QgsAttributeForm *form = qobject_cast<QgsAttributeForm *>( parent );
375 if ( form )
377
378 mExpression = config().value( u"FilterExpression"_s ).toString();
379
380 const bool allowMulti = config( u"AllowMulti"_s ).toBool();
381 const bool useCompleter = config( u"UseCompleter"_s ).toBool();
382 if ( allowMulti )
383 {
384 const bool displayGroupName = config( u"DisplayGroupName"_s ).toBool();
385 return new QgsFilteredTableWidget( parent, useCompleter, displayGroupName );
386 }
387 else if ( useCompleter )
388 {
389 return new QgsFilterLineEdit( parent );
390 }
391 else
392 {
393 QgsToolTipComboBox *combo = new QgsToolTipComboBox( parent );
394 combo->setMinimumContentsLength( 1 );
395 combo->setSizeAdjustPolicy( QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon );
396 return combo;
397 }
398}
399
401{
402 mComboBox = qobject_cast<QComboBox *>( editor );
403 mTableWidget = qobject_cast<QgsFilteredTableWidget *>( editor );
404 mLineEdit = qobject_cast<QLineEdit *>( editor );
405
406 // Read current initial form values from the editor context
408
409 if ( mComboBox )
410 {
411 mComboBox->view()->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
412 connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
413 }
414 else if ( mTableWidget )
415 {
416 connect( mTableWidget, &QgsFilteredTableWidget::itemChanged, this, static_cast<void ( QgsEditorWidgetWrapper::* )()>( &QgsEditorWidgetWrapper::emitValueChanged ), Qt::UniqueConnection );
417 }
418 else if ( mLineEdit )
419 {
420 if ( QgsFilterLineEdit *filterLineEdit = qobject_cast<QgsFilterLineEdit *>( editor ) )
421 {
422 connect( filterLineEdit, &QgsFilterLineEdit::valueChanged, this, [this]( const QString & ) {
423 if ( mSubWidgetSignalBlocking == 0 )
425 } );
426 }
427 else
428 {
429 connect( mLineEdit, &QLineEdit::textChanged, this, &QgsValueRelationWidgetWrapper::emitValueChangedInternal, Qt::UniqueConnection );
430 }
431 }
432}
433
435{
436 return mTableWidget || mLineEdit || mComboBox;
437}
438
439void QgsValueRelationWidgetWrapper::updateValues( const QVariant &value, const QVariantList & )
440{
441 updateValue( value, true );
442}
443
444void QgsValueRelationWidgetWrapper::updateValue( const QVariant &value, bool forceComboInsertion )
445{
446 if ( mTableWidget )
447 {
448 QStringList checkList;
449
450 if ( layer()->fields().at( fieldIdx() ).type() == QMetaType::Type::QVariantMap || layer()->fields().at( fieldIdx() ).type() == QMetaType::Type::QVariantList )
451 {
452 checkList = value.toStringList();
453 }
454 else
455 {
457 }
458
459 mTableWidget->checkItems( checkList );
460 }
461 else if ( mComboBox )
462 {
463 // findData fails to tell a 0 from a NULL
464 // See: "Value relation, value 0 = NULL" - https://github.com/qgis/QGIS/issues/27803
465 int idx = -1; // default to not found
466 for ( int i = 0; i < mComboBox->count(); i++ )
467 {
468 QVariant v( mComboBox->itemData( i ) );
469 if ( qgsVariantEqual( v, value ) )
470 {
471 idx = i;
472 break;
473 }
474 }
475
476 if ( idx == -1 )
477 {
478 // if value doesn't exist, we show it in '(...)' (just like value map widget)
479 if ( QgsVariantUtils::isNull( value ) || !forceComboInsertion )
480 {
481 // we might have an explicit item for null (e.g. "no selection"), if so, set to that
482 for ( int i = 0; i < mComboBox->count(); i++ )
483 {
484 if ( QgsVariantUtils::isNull( mComboBox->itemData( i ) ) )
485 {
486 idx = i;
487 break;
488 }
489 }
490 mComboBox->setCurrentIndex( idx );
491 }
492 else
493 {
494 mComboBox->addItem( value.toString().prepend( '(' ).append( ')' ), value );
495 mComboBox->setCurrentIndex( mComboBox->findData( value ) );
496 }
497 }
498 else
499 {
500 mComboBox->setCurrentIndex( idx );
501 }
502 }
503 else if ( mLineEdit )
504 {
505 mSubWidgetSignalBlocking++;
506 mLineEdit->clear();
507 bool wasFound { false };
508 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : std::as_const( mCache ) )
509 {
510 if ( i.key == value )
511 {
512 mLineEdit->setText( i.value );
513 wasFound = true;
514 break;
515 }
516 }
517 // Value could not be found
518 if ( !wasFound )
519 {
520 mLineEdit->setText( tr( "(no selection)" ) );
521 }
522 mSubWidgetSignalBlocking--;
523 }
524}
525
526void QgsValueRelationWidgetWrapper::widgetValueChanged( const QString &attribute, const QVariant &newValue, bool attributeChanged )
527{
528 // Do nothing if the value has not changed except for multi edit mode
529 // In multi edit mode feature is not updated (so attributeChanged is false) until user validate it but we need to update the
530 // value relation which could have an expression depending on another modified field
532 if ( attributeChanged || isMultieditMode )
533 {
534 QVariant oldValue( value() );
535 setFormFeatureAttribute( attribute, newValue );
536 // Update combos if the value used in the filter expression has changed
538 {
539 populate();
540 // Restore value
541 updateValue( isMultieditMode ? oldValue : value(), false );
542 // If the value has changed as a result of another widget's value change,
543 // we need to emit the signal to make sure other dependent widgets are
544 // updated.
545 QgsFields formFields( formFeature().fields() );
546
547 // Also check for fields in the layer in case this is a multi-edit form
548 // and there is not form feature set
549 if ( formFields.count() == 0 && layer() )
550 {
551 formFields = layer()->fields();
552 }
553
554 if ( oldValue != value() && fieldIdx() < formFields.count() )
555 {
556 QString attributeName( formFields.names().at( fieldIdx() ) );
557 setFormFeatureAttribute( attributeName, value() );
559 }
560 }
561 }
562}
563
564
566{
567 // No need to proceed further because the status of the object doesn't need to be updated.
568 if ( !formFeature().isValid() && !feature.isValid() && !mCache.isEmpty() )
569 {
570 return;
571 }
572 setFormFeature( feature );
573 whileBlocking( this )->populate();
574 whileBlocking( this )->setValue( feature.attribute( fieldIdx() ) );
575
576 // As we block any signals, possible depending widgets will not being updated
577 // so we force emit signal once and for all
579
580 // A bit of logic to set the default value if AllowNull is false and this is a new feature
581 // Note that this needs to be here after the cache has been created/updated by populate()
582 // and signals unblocked (we want this to propagate to the feature itself)
583 if ( context().attributeFormMode() != QgsAttributeEditorContext::Mode::MultiEditMode && !formFeature().attribute( fieldIdx() ).isValid() && !mCache.isEmpty() && !config( u"AllowNull"_s ).toBool() )
584 {
585 // This is deferred because at the time the feature is set in one widget it is not
586 // set in the next, which is typically the "down" in a drill-down
587 QTimer::singleShot( 0, this, [this] {
588 if ( !mCache.isEmpty() )
589 {
590 updateValues( formFeature().attribute( fieldIdx() ).isValid() ? formFeature().attribute( fieldIdx() ) : mCache.at( 0 ).key );
591 }
592 } );
593 }
594}
595
596int QgsValueRelationWidgetWrapper::columnCount() const
597{
598 return std::max( 1, config( u"NofColumns"_s ).toInt() );
599}
600
601
602QMetaType::Type QgsValueRelationWidgetWrapper::fkType() const
603{
605 if ( layer )
606 {
607 QgsFields fields = layer->fields();
608 int idx { fields.lookupField( config().value( u"Key"_s ).toString() ) };
609 if ( idx >= 0 )
610 {
611 return fields.at( idx ).type();
612 }
613 }
614 return QMetaType::Type::UnknownType;
615}
616
617void QgsValueRelationWidgetWrapper::populate()
618{
619 // Initialize, note that signals are blocked, to avoid double signals on new features
621 {
622 if ( context().parentFormFeature().isValid() )
623 {
624 mCache = QgsValueRelationFieldFormatter::createCache( config(), formFeature(), context().parentFormFeature() );
625 }
626 else
627 {
629 }
630 }
631 else if ( mCache.empty() )
632 {
634 }
635
636 if ( mComboBox )
637 {
638 mComboBox->blockSignals( true );
639 mComboBox->clear();
640 const bool allowNull = config( u"AllowNull"_s ).toBool();
641 if ( allowNull )
642 {
643 mComboBox->addItem( tr( "(no selection)" ), QgsVariantUtils::createNullVariant( field().type() ) );
644 }
645
646 if ( !mCache.isEmpty() )
647 {
648 QVariant currentGroup;
649 QStandardItemModel *model = qobject_cast<QStandardItemModel *>( mComboBox->model() );
650 const bool displayGroupName = config( u"DisplayGroupName"_s ).toBool();
651 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &element : std::as_const( mCache ) )
652 {
653 if ( currentGroup != element.group )
654 {
655 if ( mComboBox->count() > ( allowNull ? 1 : 0 ) )
656 {
657 mComboBox->insertSeparator( mComboBox->count() );
658 }
659 if ( displayGroupName )
660 {
661 mComboBox->addItem( element.group.toString() );
662 QStandardItem *item = model->item( mComboBox->count() - 1 );
663 item->setFlags( item->flags() & ~Qt::ItemIsEnabled );
664 }
665 currentGroup = element.group;
666 }
667
668 mComboBox->addItem( element.value, element.key );
669
670 if ( !element.description.isEmpty() )
671 {
672 mComboBox->setItemData( mComboBox->count() - 1, element.description, Qt::ToolTipRole );
673 }
674 }
675 }
676 mComboBox->blockSignals( false );
677 }
678 else if ( mTableWidget )
679 {
680 mTableWidget->setColumnCount( columnCount() );
681 mTableWidget->populate( mCache );
682 }
683 else if ( mLineEdit )
684 {
685 QStringList values;
686 values.reserve( mCache.size() );
687 for ( const QgsValueRelationFieldFormatter::ValueRelationItem &i : std::as_const( mCache ) )
688 {
689 values << i.value;
690 }
691 QStringListModel *m = new QStringListModel( values, mLineEdit );
692 QCompleter *completer = new QCompleter( m, mLineEdit );
693
694 const Qt::MatchFlags completerMatchFlags {
695 config().contains( u"CompleterMatchFlags"_s ) ? static_cast<Qt::MatchFlags>( config().value( u"CompleterMatchFlags"_s, Qt::MatchFlag::MatchStartsWith ).toInt() ) : Qt::MatchFlag::MatchStartsWith
696 };
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( u"Value"_s ).toString() == attribute || config( u"Key"_s ).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: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:7504
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7503
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6880