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