QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgsattributeform.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsattributeform.cpp
3  --------------------------------------
4  Date : 3.5.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 
16 #include "qgsattributeform.h"
17 
28 #include "qgsfeatureiterator.h"
29 #include "qgsgui.h"
30 #include "qgsproject.h"
31 #include "qgspythonrunner.h"
33 #include "qgsvectordataprovider.h"
35 #include "qgsmessagebar.h"
36 #include "qgsmessagebaritem.h"
38 #include "qgseditorwidgetwrapper.h"
39 #include "qgsrelationmanager.h"
40 #include "qgslogger.h"
41 #include "qgstabwidget.h"
42 #include "qgssettings.h"
43 #include "qgsscrollarea.h"
45 #include "qgsvectorlayerutils.h"
46 #include "qgsactionwidgetwrapper.h"
47 #include "qgsqmlwidgetwrapper.h"
48 #include "qgshtmlwidgetwrapper.h"
49 #include "qgsapplication.h"
51 #include "qgsfeaturerequest.h"
52 #include "qgstexteditwrapper.h"
53 #include "qgsfieldmodel.h"
54 #include "qgscollapsiblegroupbox.h"
55 
56 #include <QDir>
57 #include <QTextStream>
58 #include <QFileInfo>
59 #include <QFile>
60 #include <QFormLayout>
61 #include <QGridLayout>
62 #include <QGroupBox>
63 #include <QKeyEvent>
64 #include <QLabel>
65 #include <QPushButton>
66 #include <QUiLoader>
67 #include <QMessageBox>
68 #include <QToolButton>
69 #include <QMenu>
70 #include <QSvgWidget>
71 
72 int QgsAttributeForm::sFormCounter = 0;
73 
74 QgsAttributeForm::QgsAttributeForm( QgsVectorLayer *vl, const QgsFeature &feature, const QgsAttributeEditorContext &context, QWidget *parent )
75  : QWidget( parent )
76  , mLayer( vl )
77  , mOwnsMessageBar( true )
78  , mContext( context )
79  , mFormNr( sFormCounter++ )
80  , mIsSaving( false )
81  , mPreventFeatureRefresh( false )
82  , mIsSettingMultiEditFeatures( false )
83  , mUnsavedMultiEditChanges( false )
84  , mEditCommandMessage( tr( "Attributes changed" ) )
85  , mMode( QgsAttributeEditorContext::SingleEditMode )
86 {
87  init();
88  initPython();
90 
91  connect( vl, &QgsVectorLayer::updatedFields, this, &QgsAttributeForm::onUpdatedFields );
92  connect( vl, &QgsVectorLayer::beforeAddingExpressionField, this, &QgsAttributeForm::preventFeatureRefresh );
93  connect( vl, &QgsVectorLayer::beforeRemovingExpressionField, this, &QgsAttributeForm::preventFeatureRefresh );
94  connect( vl, &QgsVectorLayer::selectionChanged, this, &QgsAttributeForm::layerSelectionChanged );
95  connect( this, &QgsAttributeForm::modeChanged, this, &QgsAttributeForm::updateContainersVisibility );
96 
97  updateContainersVisibility();
98  updateLabels();
99 
100 }
101 
103 {
104  cleanPython();
105  qDeleteAll( mInterfaces );
106 }
107 
109 {
110  mButtonBox->hide();
111 
112  // Make sure that changes are taken into account if somebody tries to figure out if there have been some
115 }
116 
118 {
119  mButtonBox->show();
120 
121  disconnect( mLayer, &QgsVectorLayer::beforeModifiedCheck, this, &QgsAttributeForm::save );
122 }
123 
125 {
126  disconnect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
127  disconnect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
128 }
129 
131 {
132  mInterfaces.append( iface );
133 }
134 
136 {
137  return mFeature.isValid() && mLayer->isEditable();
138 }
139 
141 {
142  if ( mode == mMode )
143  return;
144 
146  {
147  //switching out of multi edit mode triggers a save
148  if ( mUnsavedMultiEditChanges )
149  {
150  // prompt for save
151  int res = QMessageBox::question( this, tr( "Multiedit Attributes" ),
152  tr( "Apply changes to edited features?" ), QMessageBox::Yes | QMessageBox::No );
153  if ( res == QMessageBox::Yes )
154  {
155  save();
156  }
157  }
158  clearMultiEditMessages();
159  }
160  mUnsavedMultiEditChanges = false;
161 
162  mMode = mode;
163 
164  if ( mButtonBox->isVisible() && mMode == QgsAttributeEditorContext::SingleEditMode )
165  {
167  }
168  else
169  {
170  disconnect( mLayer, &QgsVectorLayer::beforeModifiedCheck, this, &QgsAttributeForm::save );
171  }
172 
173  //update all form editor widget modes to match
174  for ( QgsAttributeFormWidget *w : std::as_const( mFormWidgets ) )
175  {
176  switch ( mode )
177  {
180  break;
181 
184  break;
185 
188  break;
189 
192  break;
193 
196  break;
197 
200  break;
201 
204  break;
205  }
206  }
207  //update all form editor widget modes to match
208  for ( QgsWidgetWrapper *w : std::as_const( mWidgets ) )
209  {
210  QgsAttributeEditorContext newContext = w->context();
211  newContext.setAttributeFormMode( mMode );
212  w->setContext( newContext );
213  }
214 
215  bool relationWidgetsVisible = ( mMode != QgsAttributeEditorContext::MultiEditMode && mMode != QgsAttributeEditorContext::AggregateSearchMode );
216  for ( QgsAttributeFormRelationEditorWidget *w : findChildren< QgsAttributeFormRelationEditorWidget * >() )
217  {
218  w->setVisible( relationWidgetsVisible );
219  }
220 
221  switch ( mode )
222  {
224  setFeature( mFeature );
225  mSearchButtonBox->setVisible( false );
226  break;
227 
229  synchronizeState();
230  mSearchButtonBox->setVisible( false );
231  break;
232 
234  synchronizeState();
235  mSearchButtonBox->setVisible( false );
236  break;
237 
239  resetMultiEdit( false );
240  synchronizeState();
241  mSearchButtonBox->setVisible( false );
242  break;
243 
245  mSearchButtonBox->setVisible( true );
246  synchronizeState();
247  hideButtonBox();
248  break;
249 
251  mSearchButtonBox->setVisible( false );
252  synchronizeState();
253  hideButtonBox();
254  break;
255 
257  setFeature( mFeature );
258  synchronizeState();
259  mSearchButtonBox->setVisible( false );
260  break;
261  }
262 
263  emit modeChanged( mMode );
264 }
265 
266 void QgsAttributeForm::changeAttribute( const QString &field, const QVariant &value, const QString &hintText )
267 {
268  const auto constMWidgets = mWidgets;
269  for ( QgsWidgetWrapper *ww : constMWidgets )
270  {
271  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
272  if ( eww )
273  {
274  if ( eww->field().name() == field )
275  {
276  eww->setValues( value, QVariantList() );
277  eww->setHint( hintText );
278  }
279  // see if the field is present in additional fields of the editor widget
280  int index = eww->additionalFields().indexOf( field );
281  if ( index >= 0 )
282  {
283  QVariant mainValue = eww->value();
284  QVariantList additionalFieldValues = eww->additionalFieldValues();
285  additionalFieldValues[index] = value;
286  eww->setValues( mainValue, additionalFieldValues );
287  eww->setHint( hintText );
288  }
289  }
290  }
291 }
292 
294 {
295  mIsSettingFeature = true;
296  mFeature = feature;
297  mCurrentFormFeature = feature;
298 
299  switch ( mMode )
300  {
305  {
306  resetValues();
307 
308  synchronizeState();
309 
310  // Settings of feature is done when we trigger the attribute form interface
311  // Issue https://github.com/qgis/QGIS/issues/29667
312  mIsSettingFeature = false;
313  const auto constMInterfaces = mInterfaces;
314  for ( QgsAttributeFormInterface *iface : constMInterfaces )
315  {
316  iface->featureChanged();
317  }
318  break;
319  }
322  {
323  resetValues();
324  break;
325  }
327  {
328  //ignore setFeature
329  break;
330  }
331  }
332  mIsSettingFeature = false;
333 }
334 
335 bool QgsAttributeForm::saveEdits( QString *error )
336 {
337  bool success = true;
338  bool changedLayer = false;
339 
340  QgsFeature updatedFeature = QgsFeature( mFeature );
341  if ( mFeature.isValid() || mMode == QgsAttributeEditorContext::AddFeatureMode )
342  {
343  bool doUpdate = false;
344 
345  // An add dialog should perform an action by default
346  // and not only if attributes have "changed"
348  doUpdate = true;
349 
350  QgsAttributes src = mFeature.attributes();
351  QgsAttributes dst = mFeature.attributes();
352 
353  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
354  {
355  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
356  if ( eww )
357  {
358  // check for invalid JSON values
359  QgsTextEditWrapper *textEdit = qobject_cast<QgsTextEditWrapper *>( eww );
360  if ( textEdit && textEdit->isInvalidJSON() )
361  {
362  if ( error )
363  *error = tr( "JSON value for %1 is invalid and has not been saved" ).arg( eww->field().name() );
364  return false;
365  }
366  QVariantList dstVars = QVariantList() << dst.at( eww->fieldIdx() );
367  QVariantList srcVars = QVariantList() << eww->value();
368  QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
369 
370  // append additional fields
371  const QStringList additionalFields = eww->additionalFields();
372  for ( const QString &fieldName : additionalFields )
373  {
374  int idx = eww->layer()->fields().lookupField( fieldName );
375  fieldIndexes << idx;
376  dstVars << dst.at( idx );
377  }
378  srcVars.append( eww->additionalFieldValues() );
379 
380  Q_ASSERT( dstVars.count() == srcVars.count() );
381 
382  for ( int i = 0; i < dstVars.count(); i++ )
383  {
384 
385  if ( !qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
386  {
387  dst[fieldIndexes[i]] = srcVars[i];
388 
389  doUpdate = true;
390  }
391  }
392  }
393  }
394 
395  updatedFeature.setAttributes( dst );
396 
397  const auto constMInterfaces = mInterfaces;
398  for ( QgsAttributeFormInterface *iface : constMInterfaces )
399  {
400  if ( !iface->acceptChanges( updatedFeature ) )
401  {
402  doUpdate = false;
403  }
404  }
405 
406  if ( doUpdate )
407  {
409  {
410  mFeature = updatedFeature;
411  }
412  else if ( mMode == QgsAttributeEditorContext::AddFeatureMode )
413  {
414  mFeature.setValid( true );
415  mLayer->beginEditCommand( mEditCommandMessage );
416  bool res = mLayer->addFeature( updatedFeature );
417  if ( res )
418  {
419  mFeature.setAttributes( updatedFeature.attributes() );
420  mLayer->endEditCommand();
422  changedLayer = true;
423  }
424  else
425  mLayer->destroyEditCommand();
426  }
427  else
428  {
429  mLayer->beginEditCommand( mEditCommandMessage );
430 
431  QgsAttributeMap newValues;
432  QgsAttributeMap oldValues;
433 
434  int n = 0;
435  for ( int i = 0; i < dst.count(); ++i )
436  {
437  if ( qgsVariantEqual( dst.at( i ), src.at( i ) ) // If field is not changed...
438  || !dst.at( i ).isValid() // or the widget returns invalid (== do not change)
439  || !fieldIsEditable( i ) ) // or the field cannot be edited ...
440  {
441  continue;
442  }
443 
444  QgsDebugMsgLevel( QStringLiteral( "Updating field %1" ).arg( i ), 2 );
445  QgsDebugMsgLevel( QStringLiteral( "dst:'%1' (type:%2, isNull:%3, isValid:%4)" )
446  .arg( dst.at( i ).toString(), dst.at( i ).typeName() ).arg( dst.at( i ).isNull() ).arg( dst.at( i ).isValid() ), 2 );
447  QgsDebugMsgLevel( QStringLiteral( "src:'%1' (type:%2, isNull:%3, isValid:%4)" )
448  .arg( src.at( i ).toString(), src.at( i ).typeName() ).arg( src.at( i ).isNull() ).arg( src.at( i ).isValid() ), 2 );
449 
450  newValues[i] = dst.at( i );
451  oldValues[i] = src.at( i );
452 
453  n++;
454  }
455 
456  success = mLayer->changeAttributeValues( mFeature.id(), newValues, oldValues );
457 
458  if ( success && n > 0 )
459  {
460  mLayer->endEditCommand();
461  mFeature.setAttributes( dst );
462  changedLayer = true;
463  }
464  else
465  {
466  mLayer->destroyEditCommand();
467  }
468  }
469  }
470  }
471 
472  emit featureSaved( updatedFeature );
473 
474  // [MD] Refresh canvas only when absolutely necessary - it interferes with other stuff (#11361).
475  // This code should be revisited - and the signals should be fired (+ layer repainted)
476  // only when actually doing any changes. I am unsure if it is actually a good idea
477  // to call save() whenever some code asks for vector layer's modified status
478  // (which is the case when attribute table is open)
479  if ( changedLayer )
480  mLayer->triggerRepaint();
481 
482  return success;
483 }
484 
485 QgsFeature QgsAttributeForm::getUpdatedFeature() const
486 {
487  // create updated Feature
488  QgsFeature updatedFeature = QgsFeature( mFeature );
489 
490  QgsAttributes featureAttributes = mFeature.attributes();
491  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
492  {
493  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
494  if ( !eww )
495  continue;
496 
497  QVariantList dstVars = QVariantList() << featureAttributes.at( eww->fieldIdx() );
498  QVariantList srcVars = QVariantList() << eww->value();
499  QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
500 
501  // append additional fields
502  const QStringList additionalFields = eww->additionalFields();
503  for ( const QString &fieldName : additionalFields )
504  {
505  int idx = eww->layer()->fields().lookupField( fieldName );
506  fieldIndexes << idx;
507  dstVars << featureAttributes.at( idx );
508  }
509  srcVars.append( eww->additionalFieldValues() );
510 
511  Q_ASSERT( dstVars.count() == srcVars.count() );
512 
513  for ( int i = 0; i < dstVars.count(); i++ )
514  {
515  if ( !qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
516  featureAttributes[fieldIndexes[i]] = srcVars[i];
517  }
518  }
519  updatedFeature.setAttributes( featureAttributes );
520 
521  return updatedFeature;
522 }
523 
524 void QgsAttributeForm::updateValuesDependencies( const int originIdx )
525 {
526  updateFieldDependencies();
527 
528  updateValuesDependenciesDefaultValues( originIdx );
529  updateValuesDependenciesVirtualFields( originIdx );
530 }
531 
532 void QgsAttributeForm::updateValuesDependenciesDefaultValues( const int originIdx )
533 {
534  if ( !mDefaultValueDependencies.contains( originIdx ) )
535  return;
536 
537  if ( !mFeature.isValid()
539  return;
540 
541  // create updated Feature
542  QgsFeature updatedFeature = getUpdatedFeature();
543 
544  // go through depending fields and update the fields with defaultexpression
545  QList<QgsWidgetWrapper *> relevantWidgets = mDefaultValueDependencies.values( originIdx );
546  for ( QgsWidgetWrapper *ww : std::as_const( relevantWidgets ) )
547  {
548  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
549  if ( eww )
550  {
551  //do not update when when mMode is not AddFeatureMode and it's not applyOnUpdate
553  {
554  continue;
555  }
556 
557  //do not update when this widget is already updating (avoid recursions)
558  if ( mAlreadyUpdatedFields.contains( eww->fieldIdx() ) )
559  continue;
560 
561  QgsExpressionContext context = createExpressionContext( updatedFeature );
562  const QVariant value = mLayer->defaultValue( eww->fieldIdx(), updatedFeature, &context );
563  eww->setValue( value );
564  mCurrentFormFeature.setAttribute( eww->field().name(), value );
565  }
566  }
567 }
568 
569 void QgsAttributeForm::updateValuesDependenciesVirtualFields( const int originIdx )
570 {
571  if ( !mVirtualFieldsDependencies.contains( originIdx ) )
572  return;
573 
574  if ( !mFeature.isValid() )
575  return;
576 
577  // create updated Feature
578  QgsFeature updatedFeature = getUpdatedFeature();
579 
580  // go through depending fields and update the virtual field with its expression
581  const QList<QgsWidgetWrapper *> relevantWidgets = mVirtualFieldsDependencies.values( originIdx );
582  for ( QgsWidgetWrapper *ww : relevantWidgets )
583  {
584  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
585  if ( !eww )
586  continue;
587 
588  //do not update when this widget is already updating (avoid recursions)
589  if ( mAlreadyUpdatedFields.contains( eww->fieldIdx() ) )
590  continue;
591 
592  // Update value
593  QgsExpressionContext context = createExpressionContext( updatedFeature );
594  QgsExpression exp( mLayer->expressionField( eww->fieldIdx() ) );
595  const QVariant value = exp.evaluate( &context );
596  updatedFeature.setAttribute( eww->fieldIdx(), value );
597  eww->setValue( value );
598  }
599 }
600 
601 void QgsAttributeForm::updateRelatedLayerFields()
602 {
603  // Synchronize dependencies
604  updateRelatedLayerFieldsDependencies();
605 
606  if ( mRelatedLayerFieldsDependencies.isEmpty() )
607  return;
608 
609  if ( !mFeature.isValid() )
610  return;
611 
612  // create updated Feature
613  QgsFeature updatedFeature = getUpdatedFeature();
614 
615  // go through depending fields and update the fields with virtual field
616  const QSet<QgsEditorWidgetWrapper *> relevantWidgets = mRelatedLayerFieldsDependencies;
617  for ( QgsEditorWidgetWrapper *eww : relevantWidgets )
618  {
619  //do not update when this widget is already updating (avoid recursions)
620  if ( mAlreadyUpdatedFields.contains( eww->fieldIdx() ) )
621  continue;
622 
623  // Update value
624  QgsExpressionContext context = createExpressionContext( updatedFeature );
625  QgsExpression exp( mLayer->expressionField( eww->fieldIdx() ) );
626  QVariant value = exp.evaluate( &context );
627  eww->setValue( value );
628  }
629 }
630 
631 void QgsAttributeForm::resetMultiEdit( bool promptToSave )
632 {
633  if ( promptToSave )
634  save();
635 
636  mUnsavedMultiEditChanges = false;
638 }
639 
640 void QgsAttributeForm::multiEditMessageClicked( const QString &link )
641 {
642  clearMultiEditMessages();
643  resetMultiEdit( link == QLatin1String( "#apply" ) );
644 }
645 
646 void QgsAttributeForm::filterTriggered()
647 {
648  QString filter = createFilterExpression();
649  emit filterExpressionSet( filter, ReplaceFilter );
650  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
652 }
653 
654 void QgsAttributeForm::searchZoomTo()
655 {
656  QString filter = createFilterExpression();
657  if ( filter.isEmpty() )
658  return;
659 
660  emit zoomToFeatures( filter );
661 }
662 
663 void QgsAttributeForm::searchFlash()
664 {
665  QString filter = createFilterExpression();
666  if ( filter.isEmpty() )
667  return;
668 
669  emit flashFeatures( filter );
670 }
671 
672 void QgsAttributeForm::filterAndTriggered()
673 {
674  QString filter = createFilterExpression();
675  if ( filter.isEmpty() )
676  return;
677 
678  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
680  emit filterExpressionSet( filter, FilterAnd );
681 }
682 
683 void QgsAttributeForm::filterOrTriggered()
684 {
685  QString filter = createFilterExpression();
686  if ( filter.isEmpty() )
687  return;
688 
689  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
691  emit filterExpressionSet( filter, FilterOr );
692 }
693 
694 void QgsAttributeForm::pushSelectedFeaturesMessage()
695 {
696  int count = mLayer->selectedFeatureCount();
697  if ( count > 0 )
698  {
699  mMessageBar->pushMessage( QString(),
700  tr( "%n matching feature(s) selected", "matching features", count ),
701  Qgis::MessageLevel::Info );
702  }
703  else
704  {
705  mMessageBar->pushMessage( QString(),
706  tr( "No matching features found" ),
707  Qgis::MessageLevel::Info );
708  }
709 }
710 
711 void QgsAttributeForm::displayWarning( const QString &message )
712 {
713  mMessageBar->pushMessage( QString(),
714  message,
715  Qgis::MessageLevel::Warning );
716 }
717 
718 void QgsAttributeForm::runSearchSelect( Qgis::SelectBehavior behavior )
719 {
720  QString filter = createFilterExpression();
721  if ( filter.isEmpty() )
722  return;
723 
724  mLayer->selectByExpression( filter, behavior );
725  pushSelectedFeaturesMessage();
726  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
728 }
729 
730 void QgsAttributeForm::searchSetSelection()
731 {
732  runSearchSelect( Qgis::SelectBehavior::SetSelection );
733 }
734 
735 void QgsAttributeForm::searchAddToSelection()
736 {
737  runSearchSelect( Qgis::SelectBehavior::AddToSelection );
738 }
739 
740 void QgsAttributeForm::searchRemoveFromSelection()
741 {
743 }
744 
745 void QgsAttributeForm::searchIntersectSelection()
746 {
747  runSearchSelect( Qgis::SelectBehavior::IntersectSelection );
748 }
749 
750 bool QgsAttributeForm::saveMultiEdits()
751 {
752  //find changed attributes
753  QgsAttributeMap newAttributeValues;
754  QMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
755  for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
756  {
757  QgsAttributeFormEditorWidget *w = wIt.value();
758  if ( !w->hasChanged() )
759  continue;
760 
761  if ( !w->currentValue().isValid() // if the widget returns invalid (== do not change)
762  || !fieldIsEditable( wIt.key() ) ) // or the field cannot be edited ...
763  {
764  continue;
765  }
766 
767  // let editor know we've accepted the changes
768  w->changesCommitted();
769 
770  newAttributeValues.insert( wIt.key(), w->currentValue() );
771  }
772 
773  if ( newAttributeValues.isEmpty() )
774  {
775  //nothing to change
776  return true;
777  }
778 
779 #if 0
780  // prompt for save
781  int res = QMessageBox::information( this, tr( "Multiedit Attributes" ),
782  tr( "Edits will be applied to all selected features." ), QMessageBox::Ok | QMessageBox::Cancel );
783  if ( res != QMessageBox::Ok )
784  {
785  resetMultiEdit();
786  return false;
787  }
788 #endif
789 
790  mLayer->beginEditCommand( tr( "Updated multiple feature attributes" ) );
791 
792  bool success = true;
793 
794  const auto constMultiEditFeatureIds = mMultiEditFeatureIds;
795  for ( QgsFeatureId fid : constMultiEditFeatureIds )
796  {
797  QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
798  for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
799  {
800  success &= mLayer->changeAttributeValue( fid, aIt.key(), aIt.value() );
801  }
802  }
803 
804  clearMultiEditMessages();
805  if ( success )
806  {
807  mLayer->endEditCommand();
808  mLayer->triggerRepaint();
809  mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Attribute changes for multiple features applied." ), Qgis::MessageLevel::Success, -1 );
810  }
811  else
812  {
813  mLayer->destroyEditCommand();
814  mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Changes could not be applied." ), Qgis::MessageLevel::Warning, 0 );
815  }
816 
817  if ( !mButtonBox->isVisible() )
818  mMessageBar->pushItem( mMultiEditMessageBarItem );
819  return success;
820 }
821 
823 {
824  return saveWithDetails( nullptr );
825 }
826 
827 bool QgsAttributeForm::saveWithDetails( QString *error )
828 {
829  if ( error )
830  error->clear();
831 
832  if ( mIsSaving )
833  return true;
834 
835  if ( mContext.formMode() == QgsAttributeEditorContext::Embed && !mValidConstraints )
836  {
837  // the feature isn't saved (as per the warning provided), but we return true
838  // so switching features still works
839  return true;
840  }
841 
842  for ( QgsWidgetWrapper *wrapper : std::as_const( mWidgets ) )
843  {
844  wrapper->notifyAboutToSave();
845  }
846 
847  // only do the dirty checks when editing an existing feature - for new
848  // features we need to add them even if the attributes are unchanged from the initial
849  // default values
850  switch ( mMode )
851  {
856  if ( !mDirty )
857  return true;
858  break;
859 
863  break;
864  }
865 
866  mIsSaving = true;
867 
868  bool success = true;
869 
870  emit beforeSave( success );
871 
872  // Somebody wants to prevent this form from saving
873  if ( !success )
874  return false;
875 
876  switch ( mMode )
877  {
884  success = saveEdits( error );
885  break;
886 
888  success = saveMultiEdits();
889  break;
890  }
891 
892  mIsSaving = false;
893  mUnsavedMultiEditChanges = false;
894  mDirty = false;
895 
896  return success;
897 }
898 
899 
901 {
902  mValuesInitialized = false;
903  const auto constMWidgets = mWidgets;
904  for ( QgsWidgetWrapper *ww : constMWidgets )
905  {
906  ww->setFeature( mFeature );
907  }
908  mValuesInitialized = true;
909  mDirty = false;
910 }
911 
913 {
914  const auto widgets { findChildren< QgsAttributeFormEditorWidget * >() };
915  for ( QgsAttributeFormEditorWidget *w : widgets )
916  {
917  w->resetSearch();
918  }
919 }
920 
921 void QgsAttributeForm::clearMultiEditMessages()
922 {
923  if ( mMultiEditUnsavedMessageBarItem )
924  {
925  if ( !mButtonBox->isVisible() )
926  mMessageBar->popWidget( mMultiEditUnsavedMessageBarItem );
927  mMultiEditUnsavedMessageBarItem = nullptr;
928  }
929  if ( mMultiEditMessageBarItem )
930  {
931  if ( !mButtonBox->isVisible() )
932  mMessageBar->popWidget( mMultiEditMessageBarItem );
933  mMultiEditMessageBarItem = nullptr;
934  }
935 }
936 
937 QString QgsAttributeForm::createFilterExpression() const
938 {
939  QStringList filters;
940  for ( QgsAttributeFormWidget *w : std::as_const( mFormWidgets ) )
941  {
942  QString filter = w->currentFilterExpression();
943  if ( !filter.isEmpty() )
944  filters << filter;
945  }
946 
947  if ( filters.isEmpty() )
948  return QString();
949 
950  QString filter = filters.join( QLatin1String( ") AND (" ) ).prepend( '(' ).append( ')' );
951  return filter;
952 }
953 
954 QgsExpressionContext QgsAttributeForm::createExpressionContext( const QgsFeature &feature ) const
955 {
956  QgsExpressionContext context;
959  if ( mExtraContextScope )
960  context.appendScope( new QgsExpressionContextScope( *mExtraContextScope.get() ) );
961  context.setFeature( feature );
962  return context;
963 }
964 
965 
966 void QgsAttributeForm::onAttributeChanged( const QVariant &value, const QVariantList &additionalFieldValues )
967 {
968  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
969  Q_ASSERT( eww );
970 
971  bool signalEmitted = false;
972 
973  if ( mValuesInitialized )
974  mDirty = true;
975 
976  mCurrentFormFeature.setAttribute( eww->field().name(), value );
977 
978  switch ( mMode )
979  {
984  {
986  emit attributeChanged( eww->field().name(), value );
988  emit widgetValueChanged( eww->field().name(), value, !mIsSettingFeature );
989 
990  // also emit the signal for additional values
991  const QStringList additionalFields = eww->additionalFields();
992  for ( int i = 0; i < additionalFields.count(); i++ )
993  {
994  const QString fieldName = additionalFields.at( i );
995  const QVariant value = additionalFieldValues.at( i );
996  emit widgetValueChanged( fieldName, value, !mIsSettingFeature );
997  }
998 
999  signalEmitted = true;
1000 
1001  if ( mValuesInitialized )
1002  updateJoinedFields( *eww );
1003 
1004  break;
1005  }
1007  {
1008  if ( !mIsSettingMultiEditFeatures )
1009  {
1010  mUnsavedMultiEditChanges = true;
1011 
1012  QLabel *msgLabel = new QLabel( tr( "Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
1013  msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
1014  msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1015  connect( msgLabel, &QLabel::linkActivated, this, &QgsAttributeForm::multiEditMessageClicked );
1016  clearMultiEditMessages();
1017 
1018  mMultiEditUnsavedMessageBarItem = new QgsMessageBarItem( msgLabel, Qgis::MessageLevel::Warning );
1019  if ( !mButtonBox->isVisible() )
1020  mMessageBar->pushItem( mMultiEditUnsavedMessageBarItem );
1021 
1022  emit widgetValueChanged( eww->field().name(), value, !mIsSettingFeature );
1023  }
1024  break;
1025  }
1028  //nothing to do
1029  break;
1030  }
1031 
1032  updateConstraints( eww );
1033 
1034  //append field index here, so it's not updated recursive
1035  mAlreadyUpdatedFields.append( eww->fieldIdx() );
1036  updateValuesDependencies( eww->fieldIdx() );
1037  mAlreadyUpdatedFields.removeAll( eww->fieldIdx() );
1038 
1039  // Updates expression controlled labels
1040  updateLabels();
1041 
1042  if ( !signalEmitted )
1043  {
1045  emit attributeChanged( eww->field().name(), value );
1047  emit widgetValueChanged( eww->field().name(), value, !mIsSettingFeature );
1048  }
1049 }
1050 
1051 void QgsAttributeForm::updateAllConstraints()
1052 {
1053  const auto constMWidgets = mWidgets;
1054  for ( QgsWidgetWrapper *ww : constMWidgets )
1055  {
1056  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1057  if ( eww )
1058  updateConstraints( eww );
1059  }
1060 }
1061 
1062 void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww )
1063 {
1064  // get the current feature set in the form
1065  QgsFeature ft;
1066  if ( currentFormValuesFeature( ft ) )
1067  {
1068  // if the layer is NOT being edited then we only check layer based constraints, and not
1069  // any constraints enforced by the provider. Because:
1070  // 1. we want to keep browsing features nice and responsive. It's nice to give feedback as to whether
1071  // the value checks out, but not if it's too slow to do so. Some constraints (e.g., unique) can be
1072  // expensive to test. A user can freely remove a layer-based constraint if it proves to be too slow
1073  // to test, but they are unlikely to have any control over provider-side constraints
1074  // 2. the provider has already accepted the value, so presumably it doesn't violate the constraint
1075  // and there's no point rechecking!
1076 
1077  // update eww constraint
1078  updateConstraint( ft, eww );
1079 
1080  // update eww dependencies constraint
1081  const QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
1082 
1083  for ( QgsEditorWidgetWrapper *depsEww : deps )
1084  updateConstraint( ft, depsEww );
1085 
1086  // sync OK button status
1087  synchronizeState();
1088 
1089  QgsExpressionContext context = createExpressionContext( ft );
1090 
1091  // Recheck visibility for all containers which are controlled by this value
1092  const QVector<ContainerInformation *> infos = mContainerInformationDependency.value( eww->field().name() );
1093  for ( ContainerInformation *info : infos )
1094  {
1095  info->apply( &context );
1096  }
1097  }
1098 }
1099 
1100 void QgsAttributeForm::updateContainersVisibility()
1101 {
1102  QgsExpressionContext context = createExpressionContext( mFeature );
1103 
1104  const QVector<ContainerInformation *> infos = mContainerVisibilityInformation;
1105 
1106  for ( ContainerInformation *info : infos )
1107  {
1108  info->apply( &context );
1109  }
1110 
1111  //and update the constraints
1112  updateAllConstraints();
1113 }
1114 
1115 void QgsAttributeForm::updateConstraint( const QgsFeature &ft, QgsEditorWidgetWrapper *eww )
1116 {
1117 
1118  if ( mContext.attributeFormMode() != QgsAttributeEditorContext::Mode::MultiEditMode )
1119  {
1120 
1122 
1123  if ( eww->layer()->fields().fieldOrigin( eww->fieldIdx() ) == QgsFields::OriginJoin )
1124  {
1125  int srcFieldIdx;
1126  const QgsVectorLayerJoinInfo *info = eww->layer()->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), eww->layer()->fields(), srcFieldIdx );
1127 
1128  if ( info && info->joinLayer() && info->isDynamicFormEnabled() )
1129  {
1130  if ( mJoinedFeatures.contains( info ) )
1131  {
1132  eww->updateConstraint( info->joinLayer(), srcFieldIdx, mJoinedFeatures[info], constraintOrigin );
1133  return;
1134  }
1135  else // if we are here, it means there's not joined field for this feature
1136  {
1137  eww->updateConstraint( QgsFeature() );
1138  return;
1139  }
1140  }
1141  }
1142  // default constraint update
1143  eww->updateConstraint( ft, constraintOrigin );
1144  }
1145 
1146 }
1147 
1148 void QgsAttributeForm::updateLabels()
1149 {
1150  if ( ! mLabelDataDefinedProperties.isEmpty() )
1151  {
1152  QgsFeature currentFeature;
1153  if ( currentFormValuesFeature( currentFeature ) )
1154  {
1155  QgsExpressionContext context = createExpressionContext( currentFeature );
1156 
1157  for ( auto it = mLabelDataDefinedProperties.constBegin() ; it != mLabelDataDefinedProperties.constEnd(); ++it )
1158  {
1159  QLabel *label { it.key() };
1160  bool ok;
1161  const QString value { it->valueAsString( context, QString(), &ok ) };
1162  if ( ok && ! value.isEmpty() )
1163  {
1164  label->setText( value );
1165  }
1166  }
1167  }
1168  }
1169 }
1170 
1171 bool QgsAttributeForm::currentFormValuesFeature( QgsFeature &feature )
1172 {
1173  bool rc = true;
1174  feature = QgsFeature( mFeature );
1176 
1177  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1178  {
1179  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1180 
1181  if ( !eww )
1182  continue;
1183 
1184  if ( dst.count() > eww->fieldIdx() )
1185  {
1186  QVariantList dstVars = QVariantList() << dst.at( eww->fieldIdx() );
1187  QVariantList srcVars = QVariantList() << eww->value();
1188  QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
1189 
1190  // append additional fields
1191  const QStringList additionalFields = eww->additionalFields();
1192  for ( const QString &fieldName : additionalFields )
1193  {
1194  int idx = eww->layer()->fields().lookupField( fieldName );
1195  fieldIndexes << idx;
1196  dstVars << dst.at( idx );
1197  }
1198  srcVars.append( eww->additionalFieldValues() );
1199 
1200  Q_ASSERT( dstVars.count() == srcVars.count() );
1201 
1202  for ( int i = 0; i < dstVars.count(); i++ )
1203  {
1204  // need to check dstVar.isNull() != srcVar.isNull()
1205  // otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
1206  if ( ( !qgsVariantEqual( dstVars[i], srcVars[i] ) || dstVars[i].isNull() != srcVars[i].isNull() ) && srcVars[i].isValid() )
1207  {
1208  dst[fieldIndexes[i]] = srcVars[i];
1209  }
1210  }
1211  }
1212  else
1213  {
1214  rc = false;
1215  break;
1216  }
1217  }
1218 
1219  feature.setAttributes( dst );
1220 
1221  return rc;
1222 }
1223 
1224 
1225 void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
1226 {
1227  mContainerVisibilityInformation.append( info );
1228 
1229  const QSet<QString> referencedColumns = info->expression.referencedColumns();
1230 
1231  for ( const QString &col : referencedColumns )
1232  {
1233  mContainerInformationDependency[ col ].append( info );
1234  }
1235 }
1236 
1237 bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions ) const
1238 {
1239  bool valid{ true };
1240 
1241  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1242  {
1243  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1244  if ( eww )
1245  {
1246  if ( ! eww->isValidConstraint() )
1247  {
1248  invalidFields.append( eww->field().displayName() );
1249 
1250  descriptions.append( eww->constraintFailureReason() );
1251 
1252  if ( eww->isBlockingCommit() )
1253  valid = false; // continue to get all invalid fields
1254  }
1255  }
1256  }
1257 
1258  return valid;
1259 }
1260 
1261 bool QgsAttributeForm::currentFormValidHardConstraints( QStringList &invalidFields, QStringList &descriptions ) const
1262 {
1263  bool valid{ true };
1264 
1265  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1266  {
1267  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1268  if ( eww )
1269  {
1270  if ( eww->isBlockingCommit() )
1271  {
1272  invalidFields.append( eww->field().displayName() );
1273  descriptions.append( eww->constraintFailureReason() );
1274  valid = false; // continue to get all invalid fields
1275  }
1276  }
1277  }
1278 
1279  return valid;
1280 }
1281 
1282 void QgsAttributeForm::onAttributeAdded( int idx )
1283 {
1284  mPreventFeatureRefresh = false;
1285  if ( mFeature.isValid() )
1286  {
1287  QgsAttributes attrs = mFeature.attributes();
1288  attrs.insert( idx, QVariant( layer()->fields().at( idx ).type() ) );
1289  mFeature.setFields( layer()->fields() );
1290  mFeature.setAttributes( attrs );
1291  }
1292  init();
1293  setFeature( mFeature );
1294 }
1295 
1296 void QgsAttributeForm::onAttributeDeleted( int idx )
1297 {
1298  mPreventFeatureRefresh = false;
1299  if ( mFeature.isValid() )
1300  {
1301  QgsAttributes attrs = mFeature.attributes();
1302  attrs.remove( idx );
1303  mFeature.setFields( layer()->fields() );
1304  mFeature.setAttributes( attrs );
1305  }
1306  init();
1307  setFeature( mFeature );
1308 }
1309 
1310 void QgsAttributeForm::onRelatedFeaturesChanged()
1311 {
1312  updateRelatedLayerFields();
1313 }
1314 
1315 void QgsAttributeForm::onUpdatedFields()
1316 {
1317  mPreventFeatureRefresh = false;
1318  if ( mFeature.isValid() )
1319  {
1320  QgsAttributes attrs( layer()->fields().size() );
1321  for ( int i = 0; i < layer()->fields().size(); i++ )
1322  {
1323  int idx = mFeature.fields().indexFromName( layer()->fields().at( i ).name() );
1324  if ( idx != -1 )
1325  {
1326  attrs[i] = mFeature.attributes().at( idx );
1327  if ( mFeature.attributes().at( idx ).type() != layer()->fields().at( i ).type() )
1328  {
1329  attrs[i].convert( layer()->fields().at( i ).type() );
1330  }
1331  }
1332  else
1333  {
1334  attrs[i] = QVariant( layer()->fields().at( i ).type() );
1335  }
1336  }
1337  mFeature.setFields( layer()->fields() );
1338  mFeature.setAttributes( attrs );
1339  }
1340  init();
1341  setFeature( mFeature );
1342 }
1343 
1344 void QgsAttributeForm::onConstraintStatusChanged( const QString &constraint,
1345  const QString &description, const QString &err, QgsEditorWidgetWrapper::ConstraintResult result )
1346 {
1347  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
1348  Q_ASSERT( eww );
1349 
1350  QgsAttributeFormEditorWidget *formEditorWidget = mFormEditorWidgets.value( eww->fieldIdx() );
1351 
1352  if ( formEditorWidget )
1353  formEditorWidget->setConstraintStatus( constraint, description, err, result );
1354 }
1355 
1356 QList<QgsEditorWidgetWrapper *> QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper *w )
1357 {
1358  QList<QgsEditorWidgetWrapper *> wDeps;
1359  QString name = w->field().name();
1360 
1361  // for each widget in the current form
1362  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1363  {
1364  // get the wrapper
1365  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1366  if ( eww )
1367  {
1368  // compare name to not compare w to itself
1369  QString ewwName = eww->field().name();
1370  if ( name != ewwName )
1371  {
1372  // get expression and referencedColumns
1373  QgsExpression expr = eww->layer()->fields().at( eww->fieldIdx() ).constraints().constraintExpression();
1374 
1375  const auto referencedColumns = expr.referencedColumns();
1376 
1377  for ( const QString &colName : referencedColumns )
1378  {
1379  if ( name == colName )
1380  {
1381  wDeps.append( eww );
1382  break;
1383  }
1384  }
1385  }
1386  }
1387  }
1388 
1389  return wDeps;
1390 }
1391 
1392 QgsRelationWidgetWrapper *QgsAttributeForm::setupRelationWidgetWrapper( const QgsRelation &rel, const QgsAttributeEditorContext &context )
1393 {
1394  return setupRelationWidgetWrapper( QString(), rel, context );
1395 }
1396 
1397 QgsRelationWidgetWrapper *QgsAttributeForm::setupRelationWidgetWrapper( const QString &relationWidgetTypeId, const QgsRelation &rel, const QgsAttributeEditorContext &context )
1398 {
1399  QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( relationWidgetTypeId, mLayer, rel, nullptr, this );
1400  const QVariantMap config = mLayer->editFormConfig().widgetConfig( rel.id() );
1401  rww->setConfig( config );
1402  rww->setContext( context );
1403 
1404  return rww;
1405 }
1406 
1407 void QgsAttributeForm::preventFeatureRefresh()
1408 {
1409  mPreventFeatureRefresh = true;
1410 }
1411 
1413 {
1414  if ( mPreventFeatureRefresh || mLayer->isEditable() || !mFeature.isValid() )
1415  return;
1416 
1417  // reload feature if layer changed although not editable
1418  // (datasource probably changed bypassing QgsVectorLayer)
1419  if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeature.id() ) ).nextFeature( mFeature ) )
1420  return;
1421 
1422  init();
1423  setFeature( mFeature );
1424 }
1425 
1426 void QgsAttributeForm::parentFormValueChanged( const QString &attribute, const QVariant &newValue )
1427 {
1428  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1429  {
1430  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1431  if ( eww )
1432  {
1433  eww->parentFormValueChanged( attribute, newValue );
1434  }
1435  }
1436 }
1437 
1439 {
1440  return mNeedsGeometry;
1441 }
1442 
1443 void QgsAttributeForm::synchronizeState()
1444 {
1445  bool isEditable = ( mFeature.isValid()
1447  || mMode == QgsAttributeEditorContext::MultiEditMode ) && mLayer->isEditable();
1448 
1449  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
1450  {
1451 
1452  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1453  if ( eww )
1454  {
1455  QgsAttributeFormEditorWidget *formWidget = mFormEditorWidgets.value( eww->fieldIdx() );
1456 
1457  if ( formWidget )
1458  formWidget->setConstraintResultVisible( isEditable );
1459 
1460  eww->setConstraintResultVisible( isEditable );
1461 
1462  bool enabled = isEditable && fieldIsEditable( eww->fieldIdx() );
1463  ww->setEnabled( enabled );
1464 
1465  updateIcon( eww );
1466  }
1467  else // handle QgsWidgetWrapper different than QgsEditorWidgetWrapper
1468  {
1469  ww->setEnabled( isEditable );
1470  }
1471 
1472  }
1473 
1474 
1476  {
1477  QStringList invalidFields, descriptions;
1478  mValidConstraints = currentFormValidHardConstraints( invalidFields, descriptions );
1479 
1480  if ( isEditable && mContext.formMode() == QgsAttributeEditorContext::Embed )
1481  {
1482  if ( !mValidConstraints && !mConstraintsFailMessageBarItem )
1483  {
1484  mConstraintsFailMessageBarItem = new QgsMessageBarItem( tr( "Changes to this form will not be saved. %n field(s) don't meet their constraints.", "invalid fields", invalidFields.size() ), Qgis::MessageLevel::Warning, -1 );
1485  mMessageBar->pushItem( mConstraintsFailMessageBarItem );
1486  }
1487  else if ( mValidConstraints && mConstraintsFailMessageBarItem )
1488  {
1489  mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1490  mConstraintsFailMessageBarItem = nullptr;
1491  }
1492  }
1493  else if ( mConstraintsFailMessageBarItem )
1494  {
1495  mMessageBar->popWidget( mConstraintsFailMessageBarItem );
1496  mConstraintsFailMessageBarItem = nullptr;
1497  }
1498 
1499  isEditable = isEditable & mValidConstraints;
1500  }
1501 
1502  // change OK button status
1503  QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1504  if ( okButton )
1505  okButton->setEnabled( isEditable );
1506 }
1507 
1508 void QgsAttributeForm::init()
1509 {
1510  QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1511 
1512  // Cleanup of any previously shown widget, we start from scratch
1513  QWidget *formWidget = nullptr;
1514  mNeedsGeometry = false;
1515 
1516  bool buttonBoxVisible = true;
1517  // Cleanup button box but preserve visibility
1518  if ( mButtonBox )
1519  {
1520  buttonBoxVisible = mButtonBox->isVisible();
1521  delete mButtonBox;
1522  mButtonBox = nullptr;
1523  }
1524 
1525  if ( mSearchButtonBox )
1526  {
1527  delete mSearchButtonBox;
1528  mSearchButtonBox = nullptr;
1529  }
1530 
1531  qDeleteAll( mWidgets );
1532  mWidgets.clear();
1533 
1534  while ( QWidget *w = this->findChild<QWidget *>() )
1535  {
1536  delete w;
1537  }
1538  delete layout();
1539 
1540  QVBoxLayout *vl = new QVBoxLayout();
1541  vl->setContentsMargins( 0, 0, 0, 0 );
1542  mMessageBar = new QgsMessageBar( this );
1543  mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1544  vl->addWidget( mMessageBar );
1545 
1546  setLayout( vl );
1547 
1548  // Get a layout
1549  QGridLayout *layout = new QGridLayout();
1550  QWidget *container = new QWidget();
1551  container->setLayout( layout );
1552  vl->addWidget( container );
1553 
1554  mFormEditorWidgets.clear();
1555  mFormWidgets.clear();
1556 
1557  // a bar to warn the user with non-blocking messages
1558  setContentsMargins( 0, 0, 0, 0 );
1559 
1560  // Try to load Ui-File for layout
1561  if ( mContext.allowCustomUi() && mLayer->editFormConfig().layout() == QgsEditFormConfig::UiFileLayout &&
1562  !mLayer->editFormConfig().uiForm().isEmpty() )
1563  {
1564  QgsDebugMsg( QStringLiteral( "loading form: %1" ).arg( mLayer->editFormConfig().uiForm() ) );
1565  const QString path = mLayer->editFormConfig().uiForm();
1567  if ( file && file->open( QFile::ReadOnly ) )
1568  {
1569  QUiLoader loader;
1570 
1571  QFileInfo fi( file->fileName() );
1572  loader.setWorkingDirectory( fi.dir() );
1573  formWidget = loader.load( file, this );
1574  if ( formWidget )
1575  {
1576  formWidget->setWindowFlags( Qt::Widget );
1577  layout->addWidget( formWidget );
1578  formWidget->show();
1579  file->close();
1580  mButtonBox = findChild<QDialogButtonBox *>();
1581  createWrappers();
1582 
1583  formWidget->installEventFilter( this );
1584  }
1585  }
1586  }
1587 
1588  QgsTabWidget *tabWidget = nullptr;
1589 
1590  // Tab layout
1591  if ( !formWidget && mLayer->editFormConfig().layout() == QgsEditFormConfig::TabLayout )
1592  {
1593  int row = 0;
1594  int column = 0;
1595  int columnCount = 1;
1596  bool hasRootFields = false;
1597  bool addSpacer = true;
1598 
1599  const QList<QgsAttributeEditorElement *> tabs = mLayer->editFormConfig().tabs();
1600 
1601  for ( QgsAttributeEditorElement *widgDef : tabs )
1602  {
1603  if ( widgDef->type() == QgsAttributeEditorElement::AeTypeContainer )
1604  {
1605  QgsAttributeEditorContainer *containerDef = dynamic_cast<QgsAttributeEditorContainer *>( widgDef );
1606  if ( !containerDef )
1607  continue;
1608 
1609  if ( containerDef->isGroupBox() )
1610  {
1611  tabWidget = nullptr;
1612  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1613  layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1614  if ( containerDef->visibilityExpression().enabled() )
1615  {
1616  registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().data() ) );
1617  }
1618  column += 2;
1619  }
1620  else
1621  {
1622  if ( !tabWidget )
1623  {
1624  tabWidget = new QgsTabWidget();
1625  layout->addWidget( tabWidget, row, column, 1, 2 );
1626  column += 2;
1627  }
1628 
1629  QWidget *tabPage = new QWidget( tabWidget );
1630 
1631  tabWidget->addTab( tabPage, widgDef->name() );
1632 
1633  if ( containerDef->visibilityExpression().enabled() )
1634  {
1635  registerContainerInformation( new ContainerInformation( tabWidget, tabPage, containerDef->visibilityExpression().data() ) );
1636  }
1637  QGridLayout *tabPageLayout = new QGridLayout();
1638  tabPage->setLayout( tabPageLayout );
1639 
1640  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1641  tabPageLayout->addWidget( widgetInfo.widget );
1642  }
1643  }
1644  else if ( widgDef->type() == QgsAttributeEditorElement::AeTypeRelation )
1645  {
1646  hasRootFields = true;
1647  tabWidget = nullptr;
1648  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1649  QgsCollapsibleGroupBox *collapsibleGroupBox = new QgsCollapsibleGroupBox();
1650 
1651  if ( widgetInfo.showLabel )
1652  collapsibleGroupBox->setTitle( widgetInfo.labelText );
1653 
1654  QVBoxLayout *collapsibleGroupBoxLayout = new QVBoxLayout();
1655  collapsibleGroupBoxLayout->addWidget( widgetInfo.widget );
1656  collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
1657 
1658  QVBoxLayout *c = new QVBoxLayout();
1659  c->addWidget( collapsibleGroupBox );
1660  layout->addLayout( c, row, column, 1, 2 );
1661  column += 2;
1662 
1663  // we consider all relation editors should be expanding
1664  addSpacer = false;
1665  }
1666  else
1667  {
1668  hasRootFields = true;
1669  tabWidget = nullptr;
1670  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1671  QLabel *label = new QLabel( widgetInfo.labelText );
1672  label->setToolTip( widgetInfo.toolTip );
1673  if ( columnCount > 1 && !widgetInfo.labelOnTop )
1674  {
1675  label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1676  }
1677 
1678  label->setBuddy( widgetInfo.widget );
1679 
1680  // If at least one expanding widget is present do not add a spacer
1681  if ( widgetInfo.widget
1682  && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
1683  && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
1684  && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
1685  addSpacer = false;
1686 
1687  if ( !widgetInfo.showLabel )
1688  {
1689  QVBoxLayout *c = new QVBoxLayout();
1690  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1691  c->addWidget( widgetInfo.widget );
1692  layout->addLayout( c, row, column, 1, 2 );
1693  column += 2;
1694  }
1695  else if ( widgetInfo.labelOnTop )
1696  {
1697  QVBoxLayout *c = new QVBoxLayout();
1698  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1699  c->addWidget( label );
1700  c->addWidget( widgetInfo.widget );
1701  layout->addLayout( c, row, column, 1, 2 );
1702  column += 2;
1703  }
1704  else
1705  {
1706  layout->addWidget( label, row, column++ );
1707  layout->addWidget( widgetInfo.widget, row, column++ );
1708  }
1709 
1710  // Alias DD overrides
1711  if ( widgDef->type() == QgsAttributeEditorElement::AttributeEditorType::AeTypeField )
1712  {
1713  const QgsAttributeEditorField *fieldElement { static_cast<QgsAttributeEditorField *>( widgDef ) };
1714  const int fieldIdx = fieldElement->idx();
1715  if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
1716  {
1717  const QString fieldName { mLayer->fields().at( fieldIdx ).name() };
1718  if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
1719  {
1720  const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
1721  if ( property.isActive() && ! property.expressionString().isEmpty() )
1722  {
1723  mLabelDataDefinedProperties[ label ] = property;
1724  }
1725  }
1726  }
1727  }
1728  }
1729 
1730  if ( column >= columnCount * 2 )
1731  {
1732  column = 0;
1733  row += 1;
1734  }
1735  }
1736 
1737  if ( hasRootFields && addSpacer )
1738  {
1739  QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1740  layout->addItem( spacerItem, row, 0 );
1741  layout->setRowStretch( row, 1 );
1742  }
1743 
1744  formWidget = container;
1745  }
1746 
1747  // Autogenerate Layout
1748  // If there is still no layout loaded (defined as autogenerate or other methods failed)
1749  mIconMap.clear();
1750 
1751  if ( !formWidget )
1752  {
1753  formWidget = new QWidget( this );
1754  QGridLayout *gridLayout = new QGridLayout( formWidget );
1755  formWidget->setLayout( gridLayout );
1756 
1757  if ( mContext.formMode() != QgsAttributeEditorContext::Embed )
1758  {
1759  // put the form into a scroll area to nicely handle cases with lots of attributes
1760  QgsScrollArea *scrollArea = new QgsScrollArea( this );
1761  scrollArea->setWidget( formWidget );
1762  scrollArea->setWidgetResizable( true );
1763  scrollArea->setFrameShape( QFrame::NoFrame );
1764  scrollArea->setFrameShadow( QFrame::Plain );
1765  scrollArea->setFocusProxy( this );
1766  layout->addWidget( scrollArea );
1767  }
1768  else
1769  {
1770  layout->addWidget( formWidget );
1771  }
1772 
1773  int row = 0;
1774 
1775  const QgsFields fields = mLayer->fields();
1776 
1777  for ( const QgsField &field : fields )
1778  {
1779  int idx = fields.lookupField( field.name() );
1780  if ( idx < 0 )
1781  continue;
1782 
1783  //show attribute alias if available
1784  QString fieldName = mLayer->attributeDisplayName( idx );
1785  QString labelText = fieldName;
1786  labelText.replace( '&', QLatin1String( "&&" ) ); // need to escape '&' or they'll be replace by _ in the label text
1787 
1788  const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, field.name() );
1789 
1790  if ( widgetSetup.type() == QLatin1String( "Hidden" ) )
1791  continue;
1792 
1793  bool labelOnTop = mLayer->editFormConfig().labelOnTop( idx );
1794 
1795  // This will also create the widget
1796  QLabel *label = new QLabel( labelText );
1797  label->setToolTip( QgsFieldModel::fieldToolTipExtended( field, mLayer ) );
1798  QSvgWidget *i = new QSvgWidget();
1799  i->setFixedSize( 18, 18 );
1800 
1801  if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
1802  {
1803  const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
1804  if ( property.isActive() && ! property.expressionString().isEmpty() )
1805  {
1806  mLabelDataDefinedProperties[ label ] = property;
1807  }
1808  }
1809 
1810  QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, idx, widgetSetup.config(), nullptr, this, mContext );
1811 
1812  QWidget *w = nullptr;
1813  if ( eww )
1814  {
1815  QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
1816  w = formWidget;
1817  mFormEditorWidgets.insert( idx, formWidget );
1818  mFormWidgets.append( formWidget );
1819  formWidget->createSearchWidgetWrappers( mContext );
1820 
1821  label->setBuddy( eww->widget() );
1822  }
1823  else
1824  {
1825  w = new QLabel( QStringLiteral( "<p style=\"color: red; font-style: italic;\">%1</p>" ).arg( tr( "Failed to create widget with type '%1'" ).arg( widgetSetup.type() ) ) );
1826  }
1827 
1828 
1829  if ( w )
1830  w->setObjectName( field.name() );
1831 
1832  if ( eww )
1833  {
1834  addWidgetWrapper( eww );
1835  mIconMap[eww->widget()] = i;
1836  }
1837 
1838  if ( labelOnTop )
1839  {
1840  gridLayout->addWidget( label, row++, 0, 1, 2 );
1841  gridLayout->addWidget( w, row++, 0, 1, 2 );
1842  gridLayout->addWidget( i, row++, 0, 1, 2 );
1843  }
1844  else
1845  {
1846  gridLayout->addWidget( label, row, 0 );
1847  gridLayout->addWidget( w, row, 1 );
1848  gridLayout->addWidget( i, row++, 2 );
1849  }
1850 
1851  }
1852 
1853  const QList<QgsRelation> relations = QgsProject::instance()->relationManager()->referencedRelations( mLayer );
1854  for ( const QgsRelation &rel : relations )
1855  {
1856  QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( QStringLiteral( "relation_editor" ), rel, mContext );
1857 
1859  formWidget->createSearchWidgetWrappers( mContext );
1860 
1861  QgsCollapsibleGroupBox *collapsibleGroupBox = new QgsCollapsibleGroupBox( rel.name() );
1862  QVBoxLayout *collapsibleGroupBoxLayout = new QVBoxLayout();
1863  collapsibleGroupBoxLayout->addWidget( formWidget );
1864  collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
1865 
1866  gridLayout->addWidget( collapsibleGroupBox, row++, 0, 1, 2 );
1867 
1868  mWidgets.append( rww );
1869  mFormWidgets.append( formWidget );
1870  }
1871 
1872  if ( QgsProject::instance()->relationManager()->referencedRelations( mLayer ).isEmpty() )
1873  {
1874  QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1875  gridLayout->addItem( spacerItem, row, 0 );
1876  gridLayout->setRowStretch( row, 1 );
1877  row++;
1878  }
1879  }
1880 
1881  updateFieldDependencies();
1882 
1883  if ( !mButtonBox )
1884  {
1885  mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1886  mButtonBox->setObjectName( QStringLiteral( "buttonBox" ) );
1887  layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
1888  }
1889  mButtonBox->setVisible( buttonBoxVisible );
1890 
1891  if ( !mSearchButtonBox )
1892  {
1893  mSearchButtonBox = new QWidget();
1894  QHBoxLayout *boxLayout = new QHBoxLayout();
1895  boxLayout->setContentsMargins( 0, 0, 0, 0 );
1896  mSearchButtonBox->setLayout( boxLayout );
1897  mSearchButtonBox->setObjectName( QStringLiteral( "searchButtonBox" ) );
1898 
1899  QPushButton *clearButton = new QPushButton( tr( "&Reset Form" ), mSearchButtonBox );
1900  connect( clearButton, &QPushButton::clicked, this, &QgsAttributeForm::resetSearch );
1901  boxLayout->addWidget( clearButton );
1902  boxLayout->addStretch( 1 );
1903 
1904  QPushButton *flashButton = new QPushButton();
1905  flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1906  flashButton->setText( tr( "&Flash Features" ) );
1907  connect( flashButton, &QToolButton::clicked, this, &QgsAttributeForm::searchFlash );
1908  boxLayout->addWidget( flashButton );
1909 
1910  QPushButton *zoomButton = new QPushButton();
1911  zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1912  zoomButton->setText( tr( "&Zoom to Features" ) );
1913  connect( zoomButton, &QToolButton::clicked, this, &QgsAttributeForm::searchZoomTo );
1914  boxLayout->addWidget( zoomButton );
1915 
1916  QToolButton *selectButton = new QToolButton();
1917  selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1918  selectButton->setText( tr( "&Select Features" ) );
1919  selectButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
1920  selectButton->setPopupMode( QToolButton::MenuButtonPopup );
1921  selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
1922  connect( selectButton, &QToolButton::clicked, this, &QgsAttributeForm::searchSetSelection );
1923  QMenu *selectMenu = new QMenu( selectButton );
1924  QAction *selectAction = new QAction( tr( "Select Features" ), selectMenu );
1925  selectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
1926  connect( selectAction, &QAction::triggered, this, &QgsAttributeForm::searchSetSelection );
1927  selectMenu->addAction( selectAction );
1928  QAction *addSelectAction = new QAction( tr( "Add to Current Selection" ), selectMenu );
1929  addSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectAdd.svg" ) ) );
1930  connect( addSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchAddToSelection );
1931  selectMenu->addAction( addSelectAction );
1932  QAction *deselectAction = new QAction( tr( "Remove from Current Selection" ), selectMenu );
1933  deselectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectRemove.svg" ) ) );
1934  connect( deselectAction, &QAction::triggered, this, &QgsAttributeForm::searchRemoveFromSelection );
1935  selectMenu->addAction( deselectAction );
1936  QAction *filterSelectAction = new QAction( tr( "Filter Current Selection" ), selectMenu );
1937  filterSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectIntersect.svg" ) ) );
1938  connect( filterSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchIntersectSelection );
1939  selectMenu->addAction( filterSelectAction );
1940  selectButton->setMenu( selectMenu );
1941  boxLayout->addWidget( selectButton );
1942 
1943  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
1944  {
1945  QToolButton *filterButton = new QToolButton();
1946  filterButton->setText( tr( "Filter Features" ) );
1947  filterButton->setPopupMode( QToolButton::MenuButtonPopup );
1948  filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1949  connect( filterButton, &QToolButton::clicked, this, &QgsAttributeForm::filterTriggered );
1950  QMenu *filterMenu = new QMenu( filterButton );
1951  QAction *filterAndAction = new QAction( tr( "Filter Within (\"AND\")" ), filterMenu );
1952  connect( filterAndAction, &QAction::triggered, this, &QgsAttributeForm::filterAndTriggered );
1953  filterMenu->addAction( filterAndAction );
1954  QAction *filterOrAction = new QAction( tr( "Extend Filter (\"OR\")" ), filterMenu );
1955  connect( filterOrAction, &QAction::triggered, this, &QgsAttributeForm::filterOrTriggered );
1956  filterMenu->addAction( filterOrAction );
1957  filterButton->setMenu( filterMenu );
1958  boxLayout->addWidget( filterButton );
1959  }
1960  else
1961  {
1962  QPushButton *closeButton = new QPushButton( tr( "Close" ), mSearchButtonBox );
1963  connect( closeButton, &QPushButton::clicked, this, &QgsAttributeForm::closed );
1964  closeButton->setShortcut( Qt::Key_Escape );
1965  boxLayout->addWidget( closeButton );
1966  }
1967 
1968  layout->addWidget( mSearchButtonBox );
1969  }
1970  mSearchButtonBox->setVisible( mMode == QgsAttributeEditorContext::SearchMode );
1971 
1972  afterWidgetInit();
1973 
1974  connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
1975  connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
1976 
1977  connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsAttributeForm::synchronizeState );
1978  connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsAttributeForm::synchronizeState );
1979 
1980  // This triggers a refresh of the form widget and gives a chance to re-format the
1981  // value to those widgets that have a different representation when in edit mode
1984 
1985 
1986  const auto constMInterfaces = mInterfaces;
1987  for ( QgsAttributeFormInterface *iface : constMInterfaces )
1988  {
1989  iface->initForm();
1990  }
1991 
1993  {
1994  hideButtonBox();
1995  }
1996 
1997  QApplication::restoreOverrideCursor();
1998 }
1999 
2000 void QgsAttributeForm::cleanPython()
2001 {
2002  if ( !mPyFormVarName.isNull() )
2003  {
2004  QString expr = QStringLiteral( "if '%1' in locals(): del %1\n" ).arg( mPyFormVarName );
2005  QgsPythonRunner::run( expr );
2006  }
2007 }
2008 
2009 void QgsAttributeForm::initPython()
2010 {
2011  cleanPython();
2012 
2013  // Init Python, if init function is not empty and the combo indicates
2014  // the source for the function code
2015  if ( !mLayer->editFormConfig().initFunction().isEmpty()
2017  {
2018 
2019  QString initFunction = mLayer->editFormConfig().initFunction();
2020  QString initFilePath = mLayer->editFormConfig().initFilePath();
2021  QString initCode;
2022 
2023  switch ( mLayer->editFormConfig().initCodeSource() )
2024  {
2026  if ( !initFilePath.isEmpty() )
2027  {
2028  QFile *inputFile = QgsApplication::instance()->networkContentFetcherRegistry()->localFile( initFilePath );
2029 
2030  if ( inputFile && inputFile->open( QFile::ReadOnly ) )
2031  {
2032  // Read it into a string
2033  QTextStream inf( inputFile );
2034 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2035  inf.setCodec( "UTF-8" );
2036 #endif
2037  initCode = inf.readAll();
2038  inputFile->close();
2039  }
2040  else // The file couldn't be opened
2041  {
2042  QgsLogger::warning( QStringLiteral( "The external python file path %1 could not be opened!" ).arg( initFilePath ) );
2043  }
2044  }
2045  else
2046  {
2047  QgsLogger::warning( QStringLiteral( "The external python file path is empty!" ) );
2048  }
2049  break;
2050 
2052  initCode = mLayer->editFormConfig().initCode();
2053  if ( initCode.isEmpty() )
2054  {
2055  QgsLogger::warning( QStringLiteral( "The python code provided in the dialog is empty!" ) );
2056  }
2057  break;
2058 
2061  // Nothing to do: the function code should be already in the environment
2062  break;
2063  }
2064 
2065  // If we have a function code, run it
2066  if ( !initCode.isEmpty() )
2067  {
2069  QgsPythonRunner::run( initCode );
2070  else
2071  mMessageBar->pushMessage( QString(),
2072  tr( "Python macro could not be run due to missing permissions." ),
2073  Qgis::MessageLevel::Warning );
2074  }
2075 
2076  QgsPythonRunner::run( QStringLiteral( "import inspect" ) );
2077  QString numArgs;
2078 
2079  // Check for eval result
2080  if ( QgsPythonRunner::eval( QStringLiteral( "len(inspect.getfullargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
2081  {
2082  static int sFormId = 0;
2083  mPyFormVarName = QStringLiteral( "_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
2084 
2085  QString form = QStringLiteral( "%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
2086  .arg( mPyFormVarName )
2087  .arg( ( quint64 ) this );
2088 
2089  QgsPythonRunner::run( form );
2090 
2091  QgsDebugMsg( QStringLiteral( "running featureForm init: %1" ).arg( mPyFormVarName ) );
2092 
2093  // Legacy
2094  if ( numArgs == QLatin1String( "3" ) )
2095  {
2096  addInterface( new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName, this ) );
2097  }
2098  else
2099  {
2100  // If we get here, it means that the function doesn't accept three arguments
2101  QMessageBox msgBox;
2102  msgBox.setText( tr( "The python init function (<code>%1</code>) does not accept three arguments as expected!<br>Please check the function name in the <b>Fields</b> tab of the layer properties." ).arg( initFunction ) );
2103  msgBox.exec();
2104 #if 0
2105  QString expr = QString( "%1(%2)" )
2106  .arg( mLayer->editFormInit() )
2107  .arg( mPyFormVarName );
2108  QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr, "QgsAttributeFormInterface" );
2109  if ( iface )
2110  addInterface( iface );
2111 #endif
2112  }
2113  }
2114  else
2115  {
2116  // If we get here, it means that inspect couldn't find the function
2117  QMessageBox msgBox;
2118  msgBox.setText( tr( "The python init function (<code>%1</code>) could not be found!<br>Please check the function name in the <b>Fields</b> tab of the layer properties." ).arg( initFunction ) );
2119  msgBox.exec();
2120  }
2121  }
2122 }
2123 
2124 QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement *widgetDef, QWidget *parent, QgsVectorLayer *vl, QgsAttributeEditorContext &context )
2125 {
2126  WidgetInfo newWidgetInfo;
2127 
2128  switch ( widgetDef->type() )
2129  {
2131  {
2132  const QgsAttributeEditorAction *elementDef = dynamic_cast<const QgsAttributeEditorAction *>( widgetDef );
2133  if ( !elementDef )
2134  break;
2135 
2136  QgsActionWidgetWrapper *actionWrapper = new QgsActionWidgetWrapper( mLayer, nullptr, this );
2137  actionWrapper->setAction( elementDef->action( vl ) );
2138  context.setAttributeFormMode( mMode );
2139  actionWrapper->setContext( context );
2140  mWidgets.append( actionWrapper );
2141  newWidgetInfo.widget = actionWrapper->widget();
2142  newWidgetInfo.showLabel = false;
2143 
2144  break;
2145  }
2146 
2148  {
2149  const QgsAttributeEditorField *fieldDef = dynamic_cast<const QgsAttributeEditorField *>( widgetDef );
2150  if ( !fieldDef )
2151  break;
2152 
2153  const QgsFields fields = vl->fields();
2154  int fldIdx = fields.lookupField( fieldDef->name() );
2155  if ( fldIdx < fields.count() && fldIdx >= 0 )
2156  {
2157  const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fieldDef->name() );
2158 
2159  QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, fldIdx, widgetSetup.config(), nullptr, this, mContext );
2160  QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
2161  mFormEditorWidgets.insert( fldIdx, formWidget );
2162  mFormWidgets.append( formWidget );
2163 
2164  formWidget->createSearchWidgetWrappers( mContext );
2165 
2166  newWidgetInfo.widget = formWidget;
2167  addWidgetWrapper( eww );
2168 
2169  newWidgetInfo.widget->setObjectName( fields.at( fldIdx ).name() );
2170  newWidgetInfo.hint = fields.at( fldIdx ).comment();
2171  }
2172 
2173  newWidgetInfo.labelOnTop = mLayer->editFormConfig().labelOnTop( fldIdx );
2174  newWidgetInfo.labelText = mLayer->attributeDisplayName( fldIdx );
2175  newWidgetInfo.labelText.replace( '&', QLatin1String( "&&" ) ); // need to escape '&' or they'll be replace by _ in the label text
2176  newWidgetInfo.toolTip = QStringLiteral( "<b>%1</b><p>%2</p>" ).arg( mLayer->attributeDisplayName( fldIdx ), newWidgetInfo.hint );
2177  newWidgetInfo.showLabel = widgetDef->showLabel();
2178 
2179  break;
2180  }
2181 
2183  {
2184  const QgsAttributeEditorRelation *relDef = static_cast<const QgsAttributeEditorRelation *>( widgetDef );
2185 
2186  QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( relDef->relationWidgetTypeId(), relDef->relation(), context );
2187 
2189  formWidget->createSearchWidgetWrappers( mContext );
2190 
2191  // This needs to be after QgsAttributeFormRelationEditorWidget creation, because the widget
2192  // does not exists yet until QgsAttributeFormRelationEditorWidget is created and the setters
2193  // below directly alter the widget and check for it.
2194  rww->setWidgetConfig( relDef->relationEditorConfiguration() );
2195  rww->setNmRelationId( relDef->nmRelationId() );
2197 
2198  mWidgets.append( rww );
2199  mFormWidgets.append( formWidget );
2200 
2201  newWidgetInfo.widget = formWidget;
2202  newWidgetInfo.showLabel = relDef->showLabel();
2203  newWidgetInfo.labelText = relDef->label();
2204  if ( newWidgetInfo.labelText.isEmpty() )
2205  newWidgetInfo.labelText = rww->relation().name();
2206  newWidgetInfo.labelOnTop = true;
2207  break;
2208  }
2209 
2211  {
2212  const QgsAttributeEditorContainer *container = dynamic_cast<const QgsAttributeEditorContainer *>( widgetDef );
2213  if ( !container )
2214  break;
2215 
2216  int columnCount = container->columnCount();
2217 
2218  if ( columnCount <= 0 )
2219  columnCount = 1;
2220 
2221  QString widgetName;
2222  QWidget *myContainer = nullptr;
2223  if ( container->isGroupBox() )
2224  {
2225  QGroupBox *groupBox = new QGroupBox( parent );
2226  widgetName = QStringLiteral( "QGroupBox" );
2227  if ( container->showLabel() )
2228  groupBox->setTitle( container->name() );
2229  myContainer = groupBox;
2230  newWidgetInfo.widget = myContainer;
2231  }
2232  else
2233  {
2234  myContainer = new QWidget();
2235 
2236  QgsScrollArea *scrollArea = new QgsScrollArea( parent );
2237 
2238  scrollArea->setWidget( myContainer );
2239  scrollArea->setWidgetResizable( true );
2240  scrollArea->setFrameShape( QFrame::NoFrame );
2241  widgetName = QStringLiteral( "QScrollArea QWidget" );
2242 
2243  newWidgetInfo.widget = scrollArea;
2244  }
2245 
2246  if ( container->backgroundColor().isValid() )
2247  {
2248  QString style {QStringLiteral( "background-color: %1;" ).arg( container->backgroundColor().name() )};
2249  newWidgetInfo.widget->setStyleSheet( style );
2250  }
2251 
2252  QGridLayout *gbLayout = new QGridLayout();
2253  myContainer->setLayout( gbLayout );
2254 
2255  int row = 0;
2256  int column = 0;
2257 
2258  const QList<QgsAttributeEditorElement *> children = container->children();
2259 
2260  for ( QgsAttributeEditorElement *childDef : children )
2261  {
2262  WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2263 
2264  if ( childDef->type() == QgsAttributeEditorElement::AeTypeContainer )
2265  {
2266  QgsAttributeEditorContainer *containerDef = static_cast<QgsAttributeEditorContainer *>( childDef );
2267  if ( containerDef->visibilityExpression().enabled() )
2268  {
2269  registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().data() ) );
2270  }
2271  }
2272 
2273  if ( widgetInfo.labelText.isNull() || ! widgetInfo.showLabel )
2274  {
2275  gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2276  column += 2;
2277  }
2278  else
2279  {
2280  QLabel *mypLabel = new QLabel( widgetInfo.labelText );
2281 
2282  // Alias DD overrides
2283  if ( childDef->type() == QgsAttributeEditorElement::AeTypeField )
2284  {
2285  const QgsAttributeEditorField *fieldDef { static_cast<QgsAttributeEditorField *>( childDef ) };
2286  const QgsFields fields = vl->fields();
2287  const int fldIdx = fieldDef->idx();
2288  if ( fldIdx < fields.count() && fldIdx >= 0 )
2289  {
2290  const QString fieldName { fields.at( fldIdx ).name() };
2291  if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
2292  {
2293  const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
2294  if ( property.isActive() && ! property.expressionString().isEmpty() )
2295  {
2296  mLabelDataDefinedProperties[ mypLabel ] = property;
2297  }
2298  }
2299  }
2300  }
2301 
2302  mypLabel->setToolTip( widgetInfo.toolTip );
2303  if ( columnCount > 1 && !widgetInfo.labelOnTop )
2304  {
2305  mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2306  }
2307 
2308  mypLabel->setBuddy( widgetInfo.widget );
2309 
2310  if ( widgetInfo.labelOnTop )
2311  {
2312  QVBoxLayout *c = new QVBoxLayout();
2313  mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2314  c->layout()->addWidget( mypLabel );
2315  c->layout()->addWidget( widgetInfo.widget );
2316  gbLayout->addLayout( c, row, column, 1, 2 );
2317  column += 2;
2318  }
2319  else
2320  {
2321  gbLayout->addWidget( mypLabel, row, column++ );
2322  gbLayout->addWidget( widgetInfo.widget, row, column++ );
2323  }
2324  }
2325 
2326  if ( column >= columnCount * 2 )
2327  {
2328  column = 0;
2329  row += 1;
2330  }
2331  }
2332  QWidget *spacer = new QWidget();
2333  spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
2334  gbLayout->addWidget( spacer, ++row, 0 );
2335  gbLayout->setRowStretch( row, 1 );
2336 
2337  newWidgetInfo.labelText = QString();
2338  newWidgetInfo.labelOnTop = true;
2339  newWidgetInfo.showLabel = widgetDef->showLabel();
2340  break;
2341  }
2342 
2344  {
2345  const QgsAttributeEditorQmlElement *elementDef = static_cast<const QgsAttributeEditorQmlElement *>( widgetDef );
2346 
2347  QgsQmlWidgetWrapper *qmlWrapper = new QgsQmlWidgetWrapper( mLayer, nullptr, this );
2348  qmlWrapper->setQmlCode( elementDef->qmlCode() );
2349  context.setAttributeFormMode( mMode );
2350  qmlWrapper->setContext( context );
2351 
2352  mWidgets.append( qmlWrapper );
2353 
2354  newWidgetInfo.widget = qmlWrapper->widget();
2355  newWidgetInfo.labelText = elementDef->name();
2356  newWidgetInfo.labelOnTop = true;
2357  newWidgetInfo.showLabel = widgetDef->showLabel();
2358  break;
2359  }
2360 
2362  {
2363  const QgsAttributeEditorHtmlElement *elementDef = static_cast<const QgsAttributeEditorHtmlElement *>( widgetDef );
2364 
2365  QgsHtmlWidgetWrapper *htmlWrapper = new QgsHtmlWidgetWrapper( mLayer, nullptr, this );
2366  context.setAttributeFormMode( mMode );
2367  htmlWrapper->setHtmlCode( elementDef->htmlCode() );
2368  htmlWrapper->reinitWidget();
2369  mWidgets.append( htmlWrapper );
2370 
2371  newWidgetInfo.widget = htmlWrapper->widget();
2372  newWidgetInfo.labelText = elementDef->name();
2373  newWidgetInfo.labelOnTop = true;
2374  newWidgetInfo.showLabel = widgetDef->showLabel();
2375  mNeedsGeometry |= htmlWrapper->needsGeometry();
2376  break;
2377  }
2378 
2379  default:
2380  QgsDebugMsg( QStringLiteral( "Unknown attribute editor widget type encountered..." ) );
2381  break;
2382  }
2383 
2384  return newWidgetInfo;
2385 }
2386 
2387 void QgsAttributeForm::addWidgetWrapper( QgsEditorWidgetWrapper *eww )
2388 {
2389  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
2390  {
2391  QgsEditorWidgetWrapper *meww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2392  if ( meww )
2393  {
2394  // if another widget wrapper exists for the same field
2395  // synchronise them
2396  if ( meww->field() == eww->field() )
2397  {
2400  break;
2401  }
2402  }
2403  }
2404 
2405  mWidgets.append( eww );
2406 }
2407 
2408 void QgsAttributeForm::createWrappers()
2409 {
2410  QList<QWidget *> myWidgets = findChildren<QWidget *>();
2411  const QList<QgsField> fields = mLayer->fields().toList();
2412 
2413  const auto constMyWidgets = myWidgets;
2414  for ( QWidget *myWidget : constMyWidgets )
2415  {
2416  // Check the widget's properties for a relation definition
2417  QVariant vRel = myWidget->property( "qgisRelation" );
2418  if ( vRel.isValid() )
2419  {
2421  QgsRelation relation = relMgr->relation( vRel.toString() );
2422  if ( relation.isValid() )
2423  {
2424  QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
2425  rww->setConfig( mLayer->editFormConfig().widgetConfig( relation.id() ) );
2426  rww->setContext( mContext );
2427  rww->widget(); // Will initialize the widget
2428  mWidgets.append( rww );
2429  }
2430  }
2431  else
2432  {
2433  const auto constFields = fields;
2434  for ( const QgsField &field : constFields )
2435  {
2436  if ( field.name() == myWidget->objectName() )
2437  {
2438  int idx = mLayer->fields().lookupField( field.name() );
2439 
2440  QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( mLayer, idx, myWidget, this, mContext );
2441  addWidgetWrapper( eww );
2442  }
2443  }
2444  }
2445  }
2446 }
2447 
2448 void QgsAttributeForm::afterWidgetInit()
2449 {
2450  bool isFirstEww = true;
2451 
2452  const auto constMWidgets = mWidgets;
2453  for ( QgsWidgetWrapper *ww : constMWidgets )
2454  {
2455  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2456 
2457  if ( eww )
2458  {
2459  if ( isFirstEww )
2460  {
2461  setFocusProxy( eww->widget() );
2462  isFirstEww = false;
2463  }
2464 
2465  connect( eww, &QgsEditorWidgetWrapper::valuesChanged, this, &QgsAttributeForm::onAttributeChanged, Qt::UniqueConnection );
2466  connect( eww, &QgsEditorWidgetWrapper::constraintStatusChanged, this, &QgsAttributeForm::onConstraintStatusChanged, Qt::UniqueConnection );
2467  }
2468  else
2469  {
2470  QgsRelationWidgetWrapper *relationWidgetWrapper = qobject_cast<QgsRelationWidgetWrapper *>( ww );
2471  if ( relationWidgetWrapper )
2472  {
2473  connect( relationWidgetWrapper, &QgsRelationWidgetWrapper::relatedFeaturesChanged, this, &QgsAttributeForm::onRelatedFeaturesChanged, static_cast<Qt::ConnectionType>( Qt::UniqueConnection | Qt::QueuedConnection ) );
2474  }
2475  }
2476  }
2477 }
2478 
2479 
2480 bool QgsAttributeForm::eventFilter( QObject *object, QEvent *e )
2481 {
2482  Q_UNUSED( object )
2483 
2484  if ( e->type() == QEvent::KeyPress )
2485  {
2486  QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( e );
2487  if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
2488  {
2489  // Re-emit to this form so it will be forwarded to parent
2490  event( e );
2491  return true;
2492  }
2493  }
2494 
2495  return false;
2496 }
2497 
2498 void QgsAttributeForm::scanForEqualAttributes( QgsFeatureIterator &fit,
2499  QSet< int > &mixedValueFields,
2500  QHash< int, QVariant > &fieldSharedValues ) const
2501 {
2502  mixedValueFields.clear();
2503  fieldSharedValues.clear();
2504 
2505  QgsFeature f;
2506  bool first = true;
2507  while ( fit.nextFeature( f ) )
2508  {
2509  for ( int i = 0; i < mLayer->fields().count(); ++i )
2510  {
2511  if ( mixedValueFields.contains( i ) )
2512  continue;
2513 
2514  if ( first )
2515  {
2516  fieldSharedValues[i] = f.attribute( i );
2517  }
2518  else
2519  {
2520  if ( fieldSharedValues.value( i ) != f.attribute( i ) )
2521  {
2522  fieldSharedValues.remove( i );
2523  mixedValueFields.insert( i );
2524  }
2525  }
2526  }
2527  first = false;
2528 
2529  if ( mixedValueFields.count() == mLayer->fields().count() )
2530  {
2531  // all attributes are mixed, no need to keep scanning
2532  break;
2533  }
2534  }
2535 }
2536 
2537 
2538 void QgsAttributeForm::layerSelectionChanged()
2539 {
2540  switch ( mMode )
2541  {
2548  break;
2549 
2551  resetMultiEdit( true );
2552  break;
2553  }
2554 }
2555 
2557 {
2558  mIsSettingMultiEditFeatures = true;
2559  mMultiEditFeatureIds = fids;
2560 
2561  if ( fids.isEmpty() )
2562  {
2563  // no selected features
2564  QMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
2565  for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
2566  {
2567  wIt.value()->initialize( QVariant() );
2568  }
2569  mIsSettingMultiEditFeatures = false;
2570  return;
2571  }
2572 
2573  QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFids( fids ) );
2574 
2575  // Scan through all features to determine which attributes are initially the same
2576  QSet< int > mixedValueFields;
2577  QHash< int, QVariant > fieldSharedValues;
2578  scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
2579 
2580  // also fetch just first feature
2581  fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFid( *fids.constBegin() ) );
2582  QgsFeature firstFeature;
2583  fit.nextFeature( firstFeature );
2584 
2585  const auto constMixedValueFields = mixedValueFields;
2586  for ( int fieldIndex : std::as_const( mixedValueFields ) )
2587  {
2588  if ( QgsAttributeFormEditorWidget *w = mFormEditorWidgets.value( fieldIndex, nullptr ) )
2589  {
2590  const QStringList additionalFields = w->editorWidget()->additionalFields();
2591  QVariantList additionalFieldValues;
2592  for ( const QString &additionalField : additionalFields )
2593  additionalFieldValues << firstFeature.attribute( additionalField );
2594  w->initialize( firstFeature.attribute( fieldIndex ), true, additionalFieldValues );
2595  }
2596  }
2597  QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
2598  for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
2599  {
2600  if ( QgsAttributeFormEditorWidget *w = mFormEditorWidgets.value( sharedValueIt.key(), nullptr ) )
2601  {
2602  bool mixed = false;
2603  const QStringList additionalFields = w->editorWidget()->additionalFields();
2604  for ( const QString &additionalField : additionalFields )
2605  {
2606  int index = mLayer->fields().indexFromName( additionalField );
2607  if ( constMixedValueFields.contains( index ) )
2608  {
2609  // if additional field are mixed, it is considered as mixed
2610  mixed = true;
2611  break;
2612  }
2613  }
2614  QVariantList additionalFieldValues;
2615  if ( mixed )
2616  {
2617  for ( const QString &additionalField : additionalFields )
2618  additionalFieldValues << firstFeature.attribute( additionalField );
2619  w->initialize( firstFeature.attribute( sharedValueIt.key() ), true, additionalFieldValues );
2620  }
2621  else
2622  {
2623  for ( const QString &additionalField : additionalFields )
2624  {
2625  int index = mLayer->fields().indexFromName( additionalField );
2626  Q_ASSERT( fieldSharedValues.contains( index ) );
2627  additionalFieldValues << fieldSharedValues.value( index );
2628  }
2629  w->initialize( sharedValueIt.value(), false, additionalFieldValues );
2630  }
2631  }
2632  }
2633  mIsSettingMultiEditFeatures = false;
2634 }
2635 
2637 {
2638  if ( mOwnsMessageBar )
2639  delete mMessageBar;
2640  mOwnsMessageBar = false;
2641  mMessageBar = messageBar;
2642 }
2643 
2645 {
2647  {
2648  Q_ASSERT( false );
2649  }
2650 
2651  QStringList filters;
2652  for ( QgsAttributeFormWidget *widget : mFormWidgets )
2653  {
2654  QString filter = widget->currentFilterExpression();
2655  if ( !filter.isNull() )
2656  filters << '(' + filter + ')';
2657  }
2658 
2659  return filters.join( QLatin1String( " AND " ) );
2660 }
2661 
2663 {
2664  mExtraContextScope.reset( extraScope );
2665 }
2666 
2667 void QgsAttributeForm::ContainerInformation::apply( QgsExpressionContext *expressionContext )
2668 {
2669  bool newVisibility = expression.evaluate( expressionContext ).toBool();
2670 
2671  if ( newVisibility != isVisible )
2672  {
2673  if ( tabWidget )
2674  {
2675  tabWidget->setTabVisible( widget, newVisibility );
2676  }
2677  else
2678  {
2679  widget->setVisible( newVisibility );
2680  }
2681 
2682  isVisible = newVisibility;
2683  }
2684 }
2685 
2686 void QgsAttributeForm::updateJoinedFields( const QgsEditorWidgetWrapper &eww )
2687 {
2688  if ( !eww.layer()->fields().exists( eww.fieldIdx() ) )
2689  return;
2690 
2691  QgsFeature formFeature;
2692  QgsField field = eww.layer()->fields().field( eww.fieldIdx() );
2693  QList<const QgsVectorLayerJoinInfo *> infos = eww.layer()->joinBuffer()->joinsWhereFieldIsId( field );
2694 
2695  if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
2696  return;
2697 
2698  const QString hint = tr( "No feature joined" );
2699  const auto constInfos = infos;
2700  for ( const QgsVectorLayerJoinInfo *info : constInfos )
2701  {
2702  if ( !info->isDynamicFormEnabled() )
2703  continue;
2704 
2705  QgsFeature joinFeature = mLayer->joinBuffer()->joinedFeatureOf( info, formFeature );
2706 
2707  mJoinedFeatures[info] = joinFeature;
2708 
2709  if ( info->hasSubset() )
2710  {
2711  const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( *info );
2712 
2713  const auto constSubsetNames = subsetNames;
2714  for ( const QString &field : constSubsetNames )
2715  {
2716  QString prefixedName = info->prefixedFieldName( field );
2717  QVariant val;
2718  QString hintText = hint;
2719 
2720  if ( joinFeature.isValid() )
2721  {
2722  val = joinFeature.attribute( field );
2723  hintText.clear();
2724  }
2725 
2726  changeAttribute( prefixedName, val, hintText );
2727  }
2728  }
2729  else
2730  {
2731  const QgsFields joinFields = joinFeature.fields();
2732  for ( const QgsField &field : joinFields )
2733  {
2734  QString prefixedName = info->prefixedFieldName( field );
2735  QVariant val;
2736  QString hintText = hint;
2737 
2738  if ( joinFeature.isValid() )
2739  {
2740  val = joinFeature.attribute( field.name() );
2741  hintText.clear();
2742  }
2743 
2744  changeAttribute( prefixedName, val, hintText );
2745  }
2746  }
2747  }
2748 }
2749 
2750 bool QgsAttributeForm::fieldIsEditable( int fieldIndex ) const
2751 {
2752  return QgsVectorLayerUtils::fieldIsEditable( mLayer, fieldIndex, mFeature );
2753 }
2754 
2755 void QgsAttributeForm::updateFieldDependencies()
2756 {
2757  mDefaultValueDependencies.clear();
2758  mVirtualFieldsDependencies.clear();
2759  mRelatedLayerFieldsDependencies.clear();
2760 
2761  //create defaultValueDependencies
2762  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
2763  {
2764  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2765  if ( ! eww )
2766  continue;
2767 
2768  updateFieldDependenciesDefaultValue( eww );
2769  updateFieldDependenciesVirtualFields( eww );
2770  updateRelatedLayerFieldsDependencies( eww );
2771  }
2772 }
2773 
2774 void QgsAttributeForm::updateFieldDependenciesDefaultValue( QgsEditorWidgetWrapper *eww )
2775 {
2777  const QSet<QString> referencedColumns = exp.referencedColumns();
2778  for ( const QString &referencedColumn : referencedColumns )
2779  {
2780  if ( referencedColumn == QgsFeatureRequest::ALL_ATTRIBUTES )
2781  {
2782  const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
2783 
2784  for ( const int id : allAttributeIds )
2785  {
2786  mDefaultValueDependencies.insertMulti( id, eww );
2787  }
2788  }
2789  else
2790  {
2791  mDefaultValueDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
2792  }
2793  }
2794 }
2795 
2796 void QgsAttributeForm::updateFieldDependenciesVirtualFields( QgsEditorWidgetWrapper *eww )
2797 {
2798  QString expressionField = eww->layer()->expressionField( eww->fieldIdx() );
2799  if ( expressionField.isEmpty() )
2800  return;
2801 
2802  QgsExpression exp( expressionField );
2803  const QSet<QString> referencedColumns = exp.referencedColumns();
2804  for ( const QString &referencedColumn : referencedColumns )
2805  {
2806  if ( referencedColumn == QgsFeatureRequest::ALL_ATTRIBUTES )
2807  {
2808  const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
2809  for ( const int id : allAttributeIds )
2810  {
2811  mVirtualFieldsDependencies.insertMulti( id, eww );
2812  }
2813  }
2814  else
2815  {
2816  mVirtualFieldsDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
2817  }
2818  }
2819 }
2820 
2821 void QgsAttributeForm::updateRelatedLayerFieldsDependencies( QgsEditorWidgetWrapper *eww )
2822 {
2823  if ( eww )
2824  {
2825  QString expressionField = eww->layer()->expressionField( eww->fieldIdx() );
2826  if ( expressionField.contains( QStringLiteral( "relation_aggregate" ) )
2827  || expressionField.contains( QStringLiteral( "get_features" ) ) )
2828  mRelatedLayerFieldsDependencies.insert( eww );
2829  }
2830  else
2831  {
2832  mRelatedLayerFieldsDependencies.clear();
2833  //create defaultValueDependencies
2834  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
2835  {
2836  QgsEditorWidgetWrapper *editorWidgetWrapper = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2837  if ( ! editorWidgetWrapper )
2838  continue;
2839 
2840  updateRelatedLayerFieldsDependencies( editorWidgetWrapper );
2841  }
2842  }
2843 }
2844 
2845 void QgsAttributeForm::updateIcon( QgsEditorWidgetWrapper *eww )
2846 {
2847  if ( !eww->widget() || !mIconMap[eww->widget()] )
2848  return;
2849 
2850  // no icon by default
2851  mIconMap[eww->widget()]->hide();
2852 
2853  if ( !eww->widget()->isEnabled() && mLayer->isEditable() )
2854  {
2855  if ( mLayer->fields().fieldOrigin( eww->fieldIdx() ) == QgsFields::OriginJoin )
2856  {
2857  int srcFieldIndex;
2858  const QgsVectorLayerJoinInfo *info = mLayer->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), mLayer->fields(), srcFieldIndex );
2859 
2860  if ( !info )
2861  return;
2862 
2863  if ( !info->isEditable() )
2864  {
2865  const QString file = QStringLiteral( "/mIconJoinNotEditable.svg" );
2866  const QString tooltip = tr( "Join settings do not allow editing" );
2867  reloadIcon( file, tooltip, mIconMap[eww->widget()] );
2868  }
2869  else if ( mMode == QgsAttributeEditorContext::AddFeatureMode && !info->hasUpsertOnEdit() )
2870  {
2871  const QString file = QStringLiteral( "mIconJoinHasNotUpsertOnEdit.svg" );
2872  const QString tooltip = tr( "Join settings do not allow upsert on edit" );
2873  reloadIcon( file, tooltip, mIconMap[eww->widget()] );
2874  }
2875  else if ( !info->joinLayer()->isEditable() )
2876  {
2877  const QString file = QStringLiteral( "/mIconJoinedLayerNotEditable.svg" );
2878  const QString tooltip = tr( "Joined layer is not toggled editable" );
2879  reloadIcon( file, tooltip, mIconMap[eww->widget()] );
2880  }
2881  }
2882  }
2883 }
2884 
2885 void QgsAttributeForm::reloadIcon( const QString &file, const QString &tooltip, QSvgWidget *sw )
2886 {
2887  sw->load( QgsApplication::iconPath( file ) );
2888  sw->setToolTip( tooltip );
2889  sw->show();
2890 }
SelectBehavior
Specifies how a selection should be applied.
Definition: qgis.h:545
@ SetSelection
Set selection, removing any existing selection.
@ AddToSelection
Add selection to current selection.
@ IntersectSelection
Modify current selection to include only select features which match.
@ RemoveFromSelection
Remove from current selection.
Wraps a button widget to launch a layer action.
void setAction(const QgsAction &action)
Sets the action.
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
static QgsNetworkContentFetcherRegistry * networkContentFetcherRegistry()
Returns the application's network content registry used for fetching temporary files during QGIS sess...
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
This element will load a layer action onto the form.
const QgsAction & action(const QgsVectorLayer *layer) const
Returns the (possibly lazy loaded) action for the given layer.
This is a container for attribute editors, used to group them visually in the attribute form if it is...
QgsOptionalExpression visibilityExpression() const
The visibility expression is used in the attribute form to show or hide this container based on an ex...
virtual bool isGroupBox() const
Returns if this container is going to be rendered as a group box.
QColor backgroundColor() const
backgroundColor
int columnCount() const
Gets the number of columns in this group.
QList< QgsAttributeEditorElement * > children() const
Gets a list of the children elements of this container.
This class contains context information for attribute editor widgets.
FormMode formMode() const
Returns the form mode.
QString attributeFormModeString() const
Returns given attributeFormMode as string.
@ Embed
A form was embedded as a widget on another form.
bool allowCustomUi() const
Returns true if the attribute editor should permit use of custom UI forms.
@ SearchMode
Form values are used for searching/filtering the layer.
@ FixAttributeMode
Fix feature mode, for modifying the feature attributes without saving. The updated feature is availab...
@ IdentifyMode
Identify the feature.
@ SingleEditMode
Single edit mode, for editing a single feature.
@ AggregateSearchMode
Form is in aggregate search mode, show each widget in this mode.
@ MultiEditMode
Multi edit mode, for editing fields of multiple features at once.
void setAttributeFormMode(const Mode &attributeFormMode)
Set attributeFormMode for the edited form.
Mode attributeFormMode() const
Returns current attributeFormMode.
This is an abstract base class for any elements of a drag and drop form.
AttributeEditorType type() const
The type of this element.
bool showLabel() const
Controls if this element should be labeled with a title (field, relation or groupname).
QString name() const
Returns the name of this element.
@ AeTypeAction
A layer action element (since QGIS 3.22)
This element will load a field's widget onto the form.
int idx() const
Returns the index of the field.
An attribute editor widget that will represent arbitrary HTML code.
QString htmlCode() const
The Html code that will be represented within this widget.
An attribute editor widget that will represent arbitrary QML code.
QString qmlCode() const
The QML code that will be represented within this widget.
This element will load a relation editor onto the form.
const QgsRelation & relation() const
Gets the id of the relation which shall be embedded.
QVariantMap relationEditorConfiguration() const
Returns the relation editor widget configuration.
QVariant nmRelationId() const
Determines the relation id of the second relation involved in an N:M relation.
bool forceSuppressFormPopup() const
Determines the force suppress form popup status.
QString relationWidgetTypeId() const
Returns the current relation widget type id.
QString label() const
Determines the label of this element.
A widget consisting of both an editor widget and additional widgets for controlling the behavior of t...
bool hasChanged() const
Returns true if the widget's value has been changed since it was initialized.
void changesCommitted()
Called when field values have been committed;.
QVariant currentValue() const
Returns the current value of the attached editor widget.
void setConstraintStatus(const QString &constraint, const QString &description, const QString &err, QgsEditorWidgetWrapper::ConstraintResult result)
Set the constraint status for this widget.
void createSearchWidgetWrappers(const QgsAttributeEditorContext &context=QgsAttributeEditorContext()) override
Creates the search widget wrappers for the widget used when the form is in search mode.
void setConstraintResultVisible(bool editable)
Set the constraint result label visible or invisible according to the layer editable status.
This class helps to support legacy open form scripts to be compatible with the new QgsAttributeForm s...
Widget to show for child relations on an attribute form.
void createSearchWidgetWrappers(const QgsAttributeEditorContext &context=QgsAttributeEditorContext()) override
Creates the search widget wrappers for the widget used when the form is in search mode.
Base class for all widgets shown on a QgsAttributeForm.
void resetSearch()
Resets the search/filter value of the widget.
virtual QString currentFilterExpression() const
Creates an expression matching the current search filter value and search properties represented in t...
@ SearchMode
Layer search/filter mode.
@ MultiEditMode
Multi edit mode, both the editor widget and a QgsMultiEditToolButton is shown.
@ DefaultMode
Default mode, only the editor widget is shown.
@ AggregateSearchMode
Embedded in a search form, show additional aggregate function toolbutton.
void showButtonBox()
Shows the button box (OK/Cancel) and disables auto-commit.
void parentFormValueChanged(const QString &attribute, const QVariant &newValue)
Is called in embedded forms when an attribute value in the parent form has changed to newValue.
void setFeature(const QgsFeature &feature)
Update all editors to correspond to a different feature.
void setMultiEditFeatureIds(const QgsFeatureIds &fids)
Sets all feature IDs which are to be edited if the form is in multiedit mode.
void refreshFeature()
reload current feature
void beforeSave(bool &ok)
Will be emitted before the feature is saved.
QgsVectorLayer * layer()
Returns the layer for which this form is shown.
bool eventFilter(QObject *object, QEvent *event) override
Intercepts keypress on custom form (escape should not close it)
bool saveWithDetails(QString *error=nullptr)
Save all the values from the editors to the layer.
void addInterface(QgsAttributeFormInterface *iface)
Takes ownership.
bool needsGeometry() const
Returns true if any of the form widgets need feature geometry.
void setExtraContextScope(QgsExpressionContextScope *extraScope)
Sets an additional expression context scope to be used for calculations in this form.
void changeAttribute(const QString &field, const QVariant &value, const QString &hintText=QString())
Call this to change the content of a given attribute.
bool save()
Save all the values from the editors to the layer.
void filterExpressionSet(const QString &expression, QgsAttributeForm::FilterType type)
Emitted when a filter expression is set using the form.
void hideButtonBox()
Hides the button box (OK/Cancel) and enables auto-commit.
const QgsFeature & feature()
void closed()
Emitted when the user selects the close option from the form's button bar.
void resetSearch()
Resets the search/filter form values.
void widgetValueChanged(const QString &attribute, const QVariant &value, bool attributeChanged)
Notifies about changes of attributes.
QString aggregateFilter() const
The aggregate filter is only useful if the form is in AggregateFilter mode.
void flashFeatures(const QString &filter)
Emitted when the user chooses to flash a filtered set of features.
void resetValues()
Sets all values to the values of the current feature.
~QgsAttributeForm() override
QgsAttributeForm(QgsVectorLayer *vl, const QgsFeature &feature=QgsFeature(), const QgsAttributeEditorContext &context=QgsAttributeEditorContext(), QWidget *parent=nullptr)
void disconnectButtonBox()
Disconnects the button box (OK/Cancel) from the accept/resetValues slots If this method is called,...
bool editable()
Returns if the form is currently in editable mode.
void setMessageBar(QgsMessageBar *messageBar)
Sets the message bar to display feedback from the form in.
void modeChanged(QgsAttributeEditorContext::Mode mode)
Emitted when the form changes mode.
void featureSaved(const QgsFeature &feature)
Emitted when a feature is changed or added.
void displayWarning(const QString &message)
Displays a warning message in the form message bar.
void zoomToFeatures(const QString &filter)
Emitted when the user chooses to zoom to a filtered set of features.
Q_DECL_DEPRECATED void attributeChanged(const QString &attribute, const QVariant &value)
Notifies about changes of attributes, this signal is not emitted when the value is set back to the or...
void setMode(QgsAttributeEditorContext::Mode mode)
Sets the current mode of the form.
@ ReplaceFilter
Filter should replace any existing filter.
@ FilterOr
Filter should be combined using "OR".
@ FilterAnd
Filter should be combined using "AND".
QgsAttributeEditorContext::Mode mode() const
Returns the current mode of the form.
A vector of attributes.
Definition: qgsattributes.h:58
A groupbox that collapses/expands when toggled and can save its collapsed and checked states.
Q_GADGET QString expression
QString initCode() const
Gets Python code for edit form initialization.
QVariantMap widgetConfig(const QString &widgetName) const
Gets the configuration for the editor widget with the given name.
QString initFilePath() const
Gets Python external file path for edit form initialization.
QgsPropertyCollection dataDefinedFieldProperties(const QString &fieldName) const
Returns data defined properties for fieldName.
@ TabLayout
Use a layout with tabs and group boxes. Needs to be configured.
@ UiFileLayout
Load a .ui file for the layout. Needs to be configured.
bool labelOnTop(int idx) const
If this returns true, the widget at the given index will receive its label on the previous line while...
QString uiForm() const
Returns the path or URL to the .ui form.
PythonInitCodeSource initCodeSource() const
Returns Python code source for edit form initialization (if it shall be loaded from a file,...
@ CodeSourceFile
Load the Python code from an external file.
@ CodeSourceEnvironment
Use the Python code available in the Python environment.
@ CodeSourceNone
Do not use Python code at all.
@ CodeSourceDialog
Use the Python code provided in the dialog.
QString initFunction() const
Gets Python function for edit form initialization.
QList< QgsAttributeEditorElement * > tabs() const
Returns a list of tabs for EditorLayout::TabLayout obtained from the invisible root container.
EditorLayout layout() const
Gets the active layout style for the attribute editor for this layer.
QgsEditorWidgetSetup findBest(const QgsVectorLayer *vl, const QString &fieldName) const
Find the best editor widget and its configuration for a given field.
QgsEditorWidgetWrapper * create(const QString &widgetId, QgsVectorLayer *vl, int fieldIdx, const QVariantMap &config, QWidget *editor, QWidget *parent, const QgsAttributeEditorContext &context=QgsAttributeEditorContext())
Create an attribute editor widget wrapper of a given type for a given field.
Holder for the widget type and its configuration for a field.
QVariantMap config() const
Manages an editor widget Widget and wrapper share the same parent.
virtual QVariant value() const =0
Will be used to access the widget's value.
virtual QVariantList additionalFieldValues() const
Will be used to access the widget's values for potential additional fields handled by the widget.
int fieldIdx() const
Access the field index.
virtual void parentFormValueChanged(const QString &attribute, const QVariant &value)
Is called in embedded form widgets when an attribute value in the parent form has changed.
virtual QStringList additionalFields() const
Returns the list of additional fields which the editor handles.
QString constraintFailureReason() const
Returns the reason why a constraint check has failed (or an empty string if constraint check was succ...
void valuesChanged(const QVariant &value, const QVariantList &additionalFieldValues=QVariantList())
Emit this signal, whenever the value changed.
bool isValidConstraint() const
Gets the current constraint status.
void updateConstraint(const QgsFeature &featureContext, QgsFieldConstraints::ConstraintOrigin constraintOrigin=QgsFieldConstraints::ConstraintOriginNotSet)
Update constraint.
void setValues(const QVariant &value, const QVariantList &additionalValues)
Is called when the value of the widget or additional field values needs to be changed.
virtual void setHint(const QString &hintText)
Add a hint text on the widget.
QgsField field() const
Access the field.
ConstraintResult
Result of constraint checks.
void constraintStatusChanged(const QString &constraint, const QString &desc, const QString &err, QgsEditorWidgetWrapper::ConstraintResult status)
Emit this signal when the constraint status changed.
virtual void setValue(const QVariant &value)
Is called when the value of the widget needs to be changed.
bool isBlockingCommit() const
Returns true if the widget is preventing the feature from being committed.
void setConstraintResultVisible(bool constraintResultVisible)
Sets whether the constraint result is visible.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * formScope(const QgsFeature &formFeature=QgsFeature(), const QString &formMode=QString())
Creates a new scope which contains functions and variables from the current attribute form/table form...
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
static const QString ALL_ATTRIBUTES
A special attribute that if set matches all attributes.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
Definition: qgsfeature.cpp:255
QgsAttributes attributes
Definition: qgsfeature.h:65
QgsFields fields
Definition: qgsfeature.h:66
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:153
void setFields(const QgsFields &fields, bool initAttributes=false)
Assigns a field map with the feature to allow attribute access by attribute name.
Definition: qgsfeature.cpp:188
void setValid(bool validity)
Sets the validity of the feature.
Definition: qgsfeature.cpp:214
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:209
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:320
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
ConstraintOrigin
Origin of constraints.
@ ConstraintOriginNotSet
Constraint is not set.
@ ConstraintOriginLayer
Constraint was set by layer.
QString constraintExpression() const
Returns the constraint expression for the field, if set.
static QString fieldToolTipExtended(const QgsField &field, const QgsVectorLayer *layer)
Returns a HTML formatted tooltip string for a field, containing details like the field name,...
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString name
Definition: qgsfield.h:60
QString displayName() const
Returns the name to use when displaying this field.
Definition: qgsfield.cpp:88
QVariant::Type type
Definition: qgsfield.h:58
QgsDefaultValue defaultValueDefinition
Definition: qgsfield.h:62
QString comment
Definition: qgsfield.h:59
QgsFieldConstraints constraints
Definition: qgsfield.h:63
Container of fields for a vector layer.
Definition: qgsfields.h:45
QList< QgsField > toList() const
Utility function to return a list of QgsField instances.
Definition: qgsfields.cpp:212
QgsAttributeList allAttributesList() const
Utility function to get list of attribute indexes.
Definition: qgsfields.cpp:371
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
@ OriginJoin
Field comes from a joined layer (originIndex / 1000 = index of the join, originIndex % 1000 = index w...
Definition: qgsfields.h:52
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
Definition: qgsfields.cpp:189
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:168
bool exists(int i) const
Returns if a field index is valid.
Definition: qgsfields.cpp:153
int size() const
Returns number of items.
Definition: qgsfields.cpp:138
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:344
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
Definition: qgsgui.cpp:83
static bool pythonMacroAllowed(void(*lambda)()=nullptr, QgsMessageBar *messageBar=nullptr)
Returns true if python macros are currently allowed to be run If the global option is to ask user,...
Definition: qgsgui.cpp:300
Wraps a QQuickWidget to display HTML code.
void reinitWidget()
Clears the content and makes new initialization.
void setHtmlCode(const QString &htmlCode)
Sets the HTML code to htmlCode.
bool needsGeometry() const
Returns true if the widget needs feature geometry.
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:122
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
void editingStarted()
Emitted when editing on this layer has started.
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
Represents an item shown within a QgsMessageBar widget.
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:61
bool popWidget(QgsMessageBarItem *item)
Remove the specified item from the bar, and display the next most recent one in the stack.
void pushMessage(const QString &text, Qgis::MessageLevel level=Qgis::MessageLevel::Info, int duration=-1)
A convenience method for pushing a message with the specified text to the bar.
void pushItem(QgsMessageBarItem *item)
Display a message item on the bar, after hiding the currently visible one and putting it in a stack.
QFile * localFile(const QString &filePathOrUrl)
Returns a QFile from a local file or to a temporary file previously fetched by the registry.
bool enabled() const
Check if this optional is enabled.
Definition: qgsoptional.h:89
T data() const
Access the payload data.
Definition: qgsoptional.h:119
QgsRelationManager * relationManager
Definition: qgsproject.h:111
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:467
bool hasProperty(int key) const override
Returns true if the collection contains a property with the specified key.
QgsProperty property(int key) const override
Returns a matching property from the collection, if one exists.
A store for object properties.
Definition: qgsproperty.h:232
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
static bool eval(const QString &command, QString &result)
Eval a Python statement.
Wraps a QQuickWidget to display QML code.
void setQmlCode(const QString &qmlCode)
writes the qmlCode into a temporary file
This class manages a set of relations between layers.
QList< QgsRelation > referencedRelations(const QgsVectorLayer *layer=nullptr) const
Gets all relations where this layer is the referenced part (i.e.
Q_INVOKABLE QgsRelation relation(const QString &id) const
Gets access to a relation by its id.
void relatedFeaturesChanged()
Emit this signal, whenever the related features changed.
QgsRelation relation() const
The relation for which this wrapper is created.
void setWidgetConfig(const QVariantMap &config)
Will set the config of this widget wrapper to the specified config.
void setForceSuppressFormPopup(bool forceSuppressFormPopup)
Sets force suppress form popup status to forceSuppressFormPopup for this widget and for the vectorLay...
void setNmRelationId(const QVariant &nmRelationId=QVariant())
Sets nmRelationId for the relation id of the second relation involved in an N:M relation.
QString name
Definition: qgsrelation.h:49
Q_GADGET QString id
Definition: qgsrelation.h:46
bool isValid
Definition: qgsrelation.h:50
A QScrollArea subclass with improved scrolling behavior.
Definition: qgsscrollarea.h:42
The QgsTabWidget class is the same as the QTabWidget but with additional methods to temporarily hide/...
Definition: qgstabwidget.h:30
void setTabVisible(QWidget *tab, bool visible)
Control the visibility for the tab with the given widget.
Wraps a text widget.
bool isInvalidJSON()
Returns whether the text edit widget contains Invalid JSON.
const QgsVectorLayerJoinInfo * joinForFieldIndex(int index, const QgsFields &fields, int &sourceFieldIndex) const
Finds the vector join for a layer field index.
QList< const QgsVectorLayerJoinInfo * > joinsWhereFieldIsId(const QgsField &field) const
Returns joins where the field of a target layer is considered as an id.
QgsFeature joinedFeatureOf(const QgsVectorLayerJoinInfo *info, const QgsFeature &feature) const
Returns the joined feature corresponding to the feature.
Defines left outer join from our vector layer to some other vector layer.
bool isDynamicFormEnabled() const
Returns whether the form has to be dynamically updated with joined fields when a feature is being cre...
bool hasUpsertOnEdit() const
Returns whether a feature created on the target layer has to impact the joined layer by creating a ne...
bool isEditable() const
Returns whether joined fields may be edited through the form of the target layer.
QgsVectorLayer * joinLayer() const
Returns joined layer (may be nullptr if the reference was set by layer ID and not resolved yet)
QStringList * joinFieldNamesSubset() const
Returns the subset of fields to be used from joined layer.
static bool fieldIsEditable(const QgsVectorLayer *layer, int fieldIndex, const QgsFeature &feature)
Tests whether a field is editable for a particular feature.
Represents a vector layer which manages a vector based data sets.
QString attributeDisplayName(int index) const
Convenience function that returns the attribute alias if defined or the field name else.
void beforeRemovingExpressionField(int idx)
Will be emitted, when an expression field is going to be deleted from this vector layer.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QString expressionField(int index) const
Returns the expression used for a given expression field.
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
void endEditCommand()
Finish edit command and add it to undo/redo stack.
void destroyEditCommand()
Destroy active command and reverts all changes in it.
QgsVectorLayerJoinBuffer * joinBuffer()
Returns the join buffer object.
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
bool changeAttributeValues(QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues=QgsAttributeMap(), bool skipDefaultValues=false)
Changes attributes' values for a feature (but does not immediately commit the changes).
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false)
Changes an attribute value for a feature (but does not immediately commit the changes).
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a single feature to the sink.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
void beforeAddingExpressionField(const QString &fieldName)
Will be emitted, when an expression field is going to be added to this vector layer.
void updatedFields()
Emitted whenever the fields available from this layer have been changed.
QVariant defaultValue(int index, const QgsFeature &feature=QgsFeature(), QgsExpressionContext *context=nullptr) const
Returns the calculated default value for the specified field index.
Q_INVOKABLE void selectByExpression(const QString &expression, Qgis::SelectBehavior behavior=Qgis::SelectBehavior::SetSelection)
Selects matching features using an expression.
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
QgsEditFormConfig editFormConfig
void beforeModifiedCheck() const
Emitted when the layer is checked for modifications. Use for last-minute additions.
Manages an editor widget Widget and wrapper share the same parent.
QWidget * widget()
Access the widget managed by this wrapper.
void setConfig(const QVariantMap &config)
Will set the config of this wrapper to the specified config.
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.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
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:274
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:1742
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:1741
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:38
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
const QgsField & field
Definition: qgsfield.h:463
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38