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