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