QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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  // we consider all relation editors should be expanding
1664  addSpacer = false;
1665  }
1666  else
1667  {
1668  hasRootFields = true;
1669  tabWidget = nullptr;
1670  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1671  QLabel *label = new QLabel( widgetInfo.labelText );
1672  label->setToolTip( widgetInfo.toolTip );
1673  if ( columnCount > 1 && !widgetInfo.labelOnTop )
1674  {
1675  label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1676  }
1677 
1678  label->setBuddy( widgetInfo.widget );
1679 
1680  // If at least one expanding widget is present do not add a spacer
1681  if ( widgetInfo.widget
1682  && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
1683  && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
1684  && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
1685  addSpacer = false;
1686 
1687  if ( !widgetInfo.showLabel )
1688  {
1689  QVBoxLayout *c = new QVBoxLayout();
1690  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1691  c->addWidget( widgetInfo.widget );
1692  layout->addLayout( c, row, column, 1, 2 );
1693  column += 2;
1694  }
1695  else if ( widgetInfo.labelOnTop )
1696  {
1697  QVBoxLayout *c = new QVBoxLayout();
1698  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1699  c->addWidget( label );
1700  c->addWidget( widgetInfo.widget );
1701  layout->addLayout( c, row, column, 1, 2 );
1702  column += 2;
1703  }
1704  else
1705  {
1706  layout->addWidget( label, row, column++ );
1707  layout->addWidget( widgetInfo.widget, row, column++ );
1708  }
1709 
1710  // Alias DD overrides
1711  if ( widgDef->type() == QgsAttributeEditorElement::AttributeEditorType::AeTypeField )
1712  {
1713  const QgsAttributeEditorField *fieldElement { static_cast<QgsAttributeEditorField *>( widgDef ) };
1714  const int fieldIdx = fieldElement->idx();
1715  if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
1716  {
1717  const QString fieldName { mLayer->fields().at( fieldIdx ).name() };
1718  if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
1719  {
1720  const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
1721  if ( property.isActive() && ! property.expressionString().isEmpty() )
1722  {
1723  mLabelDataDefinedProperties[ label ] = property;
1724  }
1725  }
1726  }
1727  }
1728  }
1729 
1730  if ( column >= columnCount * 2 )
1731  {
1732  column = 0;
1733  row += 1;
1734  }
1735  }
1736 
1737  if ( hasRootFields && addSpacer )
1738  {
1739  QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1740  layout->addItem( spacerItem, row, 0 );
1741  layout->setRowStretch( row, 1 );
1742  }
1743 
1744  formWidget = container;
1745  }
1746 
1747  // Autogenerate Layout
1748  // If there is still no layout loaded (defined as autogenerate or other methods failed)
1749  mIconMap.clear();
1750 
1751  if ( !formWidget )
1752  {
1753  formWidget = new QWidget( this );
1754  QGridLayout *gridLayout = new QGridLayout( formWidget );
1755  formWidget->setLayout( gridLayout );
1756 
1757  if ( mContext.formMode() != QgsAttributeEditorContext::Embed )
1758  {
1759  // put the form into a scroll area to nicely handle cases with lots of attributes
1760  QgsScrollArea *scrollArea = new QgsScrollArea( this );
1761  scrollArea->setWidget( formWidget );
1762  scrollArea->setWidgetResizable( true );
1763  scrollArea->setFrameShape( QFrame::NoFrame );
1764  scrollArea->setFrameShadow( QFrame::Plain );
1765  scrollArea->setFocusProxy( this );
1766  layout->addWidget( scrollArea );
1767  }
1768  else
1769  {
1770  layout->addWidget( formWidget );
1771  }
1772 
1773  int row = 0;
1774 
1775  const QgsFields fields = mLayer->fields();
1776 
1777  for ( const QgsField &field : fields )
1778  {
1779  int idx = fields.lookupField( field.name() );
1780  if ( idx < 0 )
1781  continue;
1782 
1783  //show attribute alias if available
1784  QString fieldName = mLayer->attributeDisplayName( idx );
1785  QString labelText = fieldName;
1786  labelText.replace( '&', QLatin1String( "&&" ) ); // need to escape '&' or they'll be replace by _ in the label text
1787 
1788  const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, field.name() );
1789 
1790  if ( widgetSetup.type() == QLatin1String( "Hidden" ) )
1791  continue;
1792 
1793  bool labelOnTop = mLayer->editFormConfig().labelOnTop( idx );
1794 
1795  // This will also create the widget
1796  QLabel *label = new QLabel( labelText );
1797  label->setToolTip( QgsFieldModel::fieldToolTipExtended( field, mLayer ) );
1798  QSvgWidget *i = new QSvgWidget();
1799  i->setFixedSize( 18, 18 );
1800 
1801  if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
1802  {
1803  const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
1804  if ( property.isActive() && ! property.expressionString().isEmpty() )
1805  {
1806  mLabelDataDefinedProperties[ label ] = property;
1807  }
1808  }
1809 
1810  QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, idx, widgetSetup.config(), nullptr, this, mContext );
1811 
1812  QWidget *w = nullptr;
1813  if ( eww )
1814  {
1815  QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
1816  w = formWidget;
1817  mFormEditorWidgets.insert( idx, formWidget );
1818  mFormWidgets.append( formWidget );
1819  formWidget->createSearchWidgetWrappers( mContext );
1820 
1821  label->setBuddy( eww->widget() );
1822  }
1823  else
1824  {
1825  w = new QLabel( QStringLiteral( "<p style=\"color: red; font-style: italic;\">%1</p>" ).arg( tr( "Failed to create widget with type '%1'" ).arg( widgetSetup.type() ) ) );
1826  }
1827 
1828 
1829  if ( w )
1830  w->setObjectName( field.name() );
1831 
1832  if ( eww )
1833  {
1834  addWidgetWrapper( eww );
1835  mIconMap[eww->widget()] = i;
1836  }
1837 
1838  if ( labelOnTop )
1839  {
1840  gridLayout->addWidget( label, row++, 0, 1, 2 );
1841  gridLayout->addWidget( w, row++, 0, 1, 2 );
1842  gridLayout->addWidget( i, row++, 0, 1, 2 );
1843  }
1844  else
1845  {
1846  gridLayout->addWidget( label, row, 0 );
1847  gridLayout->addWidget( w, row, 1 );
1848  gridLayout->addWidget( i, row++, 2 );
1849  }
1850 
1851  }
1852 
1853  const QList<QgsRelation> relations = QgsProject::instance()->relationManager()->referencedRelations( mLayer );
1854  for ( const QgsRelation &rel : relations )
1855  {
1856  QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( QStringLiteral( "relation_editor" ), rel, mContext );
1857 
1859  formWidget->createSearchWidgetWrappers( mContext );
1860 
1861  QgsCollapsibleGroupBox *collapsibleGroupBox = new QgsCollapsibleGroupBox( rel.name() );
1862  QVBoxLayout *collapsibleGroupBoxLayout = new QVBoxLayout();
1863  collapsibleGroupBoxLayout->addWidget( formWidget );
1864  collapsibleGroupBox->setLayout( collapsibleGroupBoxLayout );
1865 
1866  gridLayout->addWidget( collapsibleGroupBox, row++, 0, 1, 2 );
1867 
1868  mWidgets.append( rww );
1869  mFormWidgets.append( formWidget );
1870  }
1871 
1872  if ( QgsProject::instance()->relationManager()->referencedRelations( mLayer ).isEmpty() )
1873  {
1874  QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1875  gridLayout->addItem( spacerItem, row, 0 );
1876  gridLayout->setRowStretch( row, 1 );
1877  row++;
1878  }
1879  }
1880 
1881  updateFieldDependencies();
1882 
1883  if ( !mButtonBox )
1884  {
1885  mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1886  mButtonBox->setObjectName( QStringLiteral( "buttonBox" ) );
1887  layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
1888  }
1889  mButtonBox->setVisible( buttonBoxVisible );
1890 
1891  if ( !mSearchButtonBox )
1892  {
1893  mSearchButtonBox = new QWidget();
1894  QHBoxLayout *boxLayout = new QHBoxLayout();
1895  boxLayout->setContentsMargins( 0, 0, 0, 0 );
1896  mSearchButtonBox->setLayout( boxLayout );
1897  mSearchButtonBox->setObjectName( QStringLiteral( "searchButtonBox" ) );
1898 
1899  QPushButton *clearButton = new QPushButton( tr( "&Reset Form" ), mSearchButtonBox );
1900  connect( clearButton, &QPushButton::clicked, this, &QgsAttributeForm::resetSearch );
1901  boxLayout->addWidget( clearButton );
1902  boxLayout->addStretch( 1 );
1903 
1904  QPushButton *flashButton = new QPushButton();
1905  flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1906  flashButton->setText( tr( "&Flash Features" ) );
1907  connect( flashButton, &QToolButton::clicked, this, &QgsAttributeForm::searchFlash );
1908  boxLayout->addWidget( flashButton );
1909 
1910  QPushButton *openAttributeTableButton = new QPushButton();
1911  openAttributeTableButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1912  openAttributeTableButton->setText( tr( "Show in &Table" ) );
1913  openAttributeTableButton->setToolTip( tr( "Open the attribute table editor with the filtered features" ) );
1914  connect( openAttributeTableButton, &QToolButton::clicked, this, [ = ]
1915  {
1916  emit openFilteredFeaturesAttributeTable( createFilterExpression() );
1917  } );
1918  boxLayout->addWidget( openAttributeTableButton );
1919 
1920  QPushButton *zoomButton = new QPushButton();
1921  zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1922  zoomButton->setText( tr( "&Zoom to Features" ) );
1923  connect( zoomButton, &QToolButton::clicked, this, &QgsAttributeForm::searchZoomTo );
1924  boxLayout->addWidget( zoomButton );
1925 
1926  QToolButton *selectButton = new QToolButton();
1927  selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1928  selectButton->setText( tr( "&Select Features" ) );
1929  selectButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
1930  selectButton->setPopupMode( QToolButton::MenuButtonPopup );
1931  selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
1932  connect( selectButton, &QToolButton::clicked, this, &QgsAttributeForm::searchSetSelection );
1933  QMenu *selectMenu = new QMenu( selectButton );
1934  QAction *selectAction = new QAction( tr( "Select Features" ), selectMenu );
1935  selectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
1936  connect( selectAction, &QAction::triggered, this, &QgsAttributeForm::searchSetSelection );
1937  selectMenu->addAction( selectAction );
1938  QAction *addSelectAction = new QAction( tr( "Add to Current Selection" ), selectMenu );
1939  addSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectAdd.svg" ) ) );
1940  connect( addSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchAddToSelection );
1941  selectMenu->addAction( addSelectAction );
1942  QAction *deselectAction = new QAction( tr( "Remove from Current Selection" ), selectMenu );
1943  deselectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectRemove.svg" ) ) );
1944  connect( deselectAction, &QAction::triggered, this, &QgsAttributeForm::searchRemoveFromSelection );
1945  selectMenu->addAction( deselectAction );
1946  QAction *filterSelectAction = new QAction( tr( "Filter Current Selection" ), selectMenu );
1947  filterSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectIntersect.svg" ) ) );
1948  connect( filterSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchIntersectSelection );
1949  selectMenu->addAction( filterSelectAction );
1950  selectButton->setMenu( selectMenu );
1951  boxLayout->addWidget( selectButton );
1952 
1953  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
1954  {
1955  QToolButton *filterButton = new QToolButton();
1956  filterButton->setText( tr( "Filter Features" ) );
1957  filterButton->setPopupMode( QToolButton::MenuButtonPopup );
1958  filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1959  connect( filterButton, &QToolButton::clicked, this, &QgsAttributeForm::filterTriggered );
1960  QMenu *filterMenu = new QMenu( filterButton );
1961  QAction *filterAndAction = new QAction( tr( "Filter Within (\"AND\")" ), filterMenu );
1962  connect( filterAndAction, &QAction::triggered, this, &QgsAttributeForm::filterAndTriggered );
1963  filterMenu->addAction( filterAndAction );
1964  QAction *filterOrAction = new QAction( tr( "Extend Filter (\"OR\")" ), filterMenu );
1965  connect( filterOrAction, &QAction::triggered, this, &QgsAttributeForm::filterOrTriggered );
1966  filterMenu->addAction( filterOrAction );
1967  filterButton->setMenu( filterMenu );
1968  boxLayout->addWidget( filterButton );
1969  }
1970  else
1971  {
1972  QPushButton *closeButton = new QPushButton( tr( "Close" ), mSearchButtonBox );
1973  connect( closeButton, &QPushButton::clicked, this, &QgsAttributeForm::closed );
1974  closeButton->setShortcut( Qt::Key_Escape );
1975  boxLayout->addWidget( closeButton );
1976  }
1977 
1978  layout->addWidget( mSearchButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
1979  }
1980  mSearchButtonBox->setVisible( mMode == QgsAttributeEditorContext::SearchMode );
1981 
1982  afterWidgetInit();
1983 
1984  connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
1985  connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
1986 
1987  connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsAttributeForm::synchronizeState );
1988  connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsAttributeForm::synchronizeState );
1989 
1990  // This triggers a refresh of the form widget and gives a chance to re-format the
1991  // value to those widgets that have a different representation when in edit mode
1994 
1995 
1996  const auto constMInterfaces = mInterfaces;
1997  for ( QgsAttributeFormInterface *iface : constMInterfaces )
1998  {
1999  iface->initForm();
2000  }
2001 
2003  {
2004  hideButtonBox();
2005  }
2006 
2007  QApplication::restoreOverrideCursor();
2008 }
2009 
2010 void QgsAttributeForm::cleanPython()
2011 {
2012  if ( !mPyFormVarName.isNull() )
2013  {
2014  QString expr = QStringLiteral( "if '%1' in locals(): del %1\n" ).arg( mPyFormVarName );
2015  QgsPythonRunner::run( expr );
2016  }
2017 }
2018 
2019 void QgsAttributeForm::initPython()
2020 {
2021  cleanPython();
2022 
2023  // Init Python, if init function is not empty and the combo indicates
2024  // the source for the function code
2025  if ( !mLayer->editFormConfig().initFunction().isEmpty()
2027  {
2028 
2029  QString initFunction = mLayer->editFormConfig().initFunction();
2030  QString initFilePath = mLayer->editFormConfig().initFilePath();
2031  QString initCode;
2032 
2033  switch ( mLayer->editFormConfig().initCodeSource() )
2034  {
2036  if ( !initFilePath.isEmpty() )
2037  {
2038  QFile *inputFile = QgsApplication::networkContentFetcherRegistry()->localFile( initFilePath );
2039 
2040  if ( inputFile && inputFile->open( QFile::ReadOnly ) )
2041  {
2042  // Read it into a string
2043  QTextStream inf( inputFile );
2044 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2045  inf.setCodec( "UTF-8" );
2046 #endif
2047  initCode = inf.readAll();
2048  inputFile->close();
2049  }
2050  else // The file couldn't be opened
2051  {
2052  QgsLogger::warning( QStringLiteral( "The external python file path %1 could not be opened!" ).arg( initFilePath ) );
2053  }
2054  }
2055  else
2056  {
2057  QgsLogger::warning( QStringLiteral( "The external python file path is empty!" ) );
2058  }
2059  break;
2060 
2062  initCode = mLayer->editFormConfig().initCode();
2063  if ( initCode.isEmpty() )
2064  {
2065  QgsLogger::warning( QStringLiteral( "The python code provided in the dialog is empty!" ) );
2066  }
2067  break;
2068 
2071  // Nothing to do: the function code should be already in the environment
2072  break;
2073  }
2074 
2075  // If we have a function code, run it
2076  if ( !initCode.isEmpty() )
2077  {
2079  QgsPythonRunner::run( initCode );
2080  else
2081  mMessageBar->pushMessage( QString(),
2082  tr( "Python macro could not be run due to missing permissions." ),
2083  Qgis::MessageLevel::Warning );
2084  }
2085 
2086  QgsPythonRunner::run( QStringLiteral( "import inspect" ) );
2087  QString numArgs;
2088 
2089  // Check for eval result
2090  if ( QgsPythonRunner::eval( QStringLiteral( "len(inspect.getfullargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
2091  {
2092  static int sFormId = 0;
2093  mPyFormVarName = QStringLiteral( "_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
2094 
2095  QString form = QStringLiteral( "%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
2096  .arg( mPyFormVarName )
2097  .arg( ( quint64 ) this );
2098 
2099  QgsPythonRunner::run( form );
2100 
2101  QgsDebugMsg( QStringLiteral( "running featureForm init: %1" ).arg( mPyFormVarName ) );
2102 
2103  // Legacy
2104  if ( numArgs == QLatin1String( "3" ) )
2105  {
2106  addInterface( new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName, this ) );
2107  }
2108  else
2109  {
2110  // If we get here, it means that the function doesn't accept three arguments
2111  QMessageBox msgBox;
2112  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 ) );
2113  msgBox.exec();
2114 #if 0
2115  QString expr = QString( "%1(%2)" )
2116  .arg( mLayer->editFormInit() )
2117  .arg( mPyFormVarName );
2118  QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr, "QgsAttributeFormInterface" );
2119  if ( iface )
2120  addInterface( iface );
2121 #endif
2122  }
2123  }
2124  else
2125  {
2126  // If we get here, it means that inspect couldn't find the function
2127  QMessageBox msgBox;
2128  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 ) );
2129  msgBox.exec();
2130  }
2131  }
2132 }
2133 
2134 QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement *widgetDef, QWidget *parent, QgsVectorLayer *vl, QgsAttributeEditorContext &context )
2135 {
2136  WidgetInfo newWidgetInfo;
2137 
2138  switch ( widgetDef->type() )
2139  {
2141  {
2142  const QgsAttributeEditorAction *elementDef = dynamic_cast<const QgsAttributeEditorAction *>( widgetDef );
2143  if ( !elementDef )
2144  break;
2145 
2146  QgsActionWidgetWrapper *actionWrapper = new QgsActionWidgetWrapper( mLayer, nullptr, this );
2147  actionWrapper->setAction( elementDef->action( vl ) );
2148  context.setAttributeFormMode( mMode );
2149  actionWrapper->setContext( context );
2150  mWidgets.append( actionWrapper );
2151  newWidgetInfo.widget = actionWrapper->widget();
2152  newWidgetInfo.showLabel = false;
2153 
2154  break;
2155  }
2156 
2158  {
2159  const QgsAttributeEditorField *fieldDef = dynamic_cast<const QgsAttributeEditorField *>( widgetDef );
2160  if ( !fieldDef )
2161  break;
2162 
2163  const QgsFields fields = vl->fields();
2164  int fldIdx = fields.lookupField( fieldDef->name() );
2165  if ( fldIdx < fields.count() && fldIdx >= 0 )
2166  {
2167  const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fieldDef->name() );
2168 
2169  QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, fldIdx, widgetSetup.config(), nullptr, this, mContext );
2170  QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
2171  mFormEditorWidgets.insert( fldIdx, formWidget );
2172  mFormWidgets.append( formWidget );
2173 
2174  formWidget->createSearchWidgetWrappers( mContext );
2175 
2176  newWidgetInfo.widget = formWidget;
2177  addWidgetWrapper( eww );
2178 
2179  newWidgetInfo.widget->setObjectName( fields.at( fldIdx ).name() );
2180  newWidgetInfo.hint = fields.at( fldIdx ).comment();
2181  }
2182 
2183  newWidgetInfo.labelOnTop = mLayer->editFormConfig().labelOnTop( fldIdx );
2184  newWidgetInfo.labelText = mLayer->attributeDisplayName( fldIdx );
2185  newWidgetInfo.labelText.replace( '&', QLatin1String( "&&" ) ); // need to escape '&' or they'll be replace by _ in the label text
2186  newWidgetInfo.toolTip = QStringLiteral( "<b>%1</b><p>%2</p>" ).arg( mLayer->attributeDisplayName( fldIdx ), newWidgetInfo.hint );
2187  newWidgetInfo.showLabel = widgetDef->showLabel();
2188 
2189  break;
2190  }
2191 
2193  {
2194  const QgsAttributeEditorRelation *relDef = static_cast<const QgsAttributeEditorRelation *>( widgetDef );
2195 
2196  QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( relDef->relationWidgetTypeId(), relDef->relation(), context );
2197 
2199  formWidget->createSearchWidgetWrappers( mContext );
2200 
2201  // This needs to be after QgsAttributeFormRelationEditorWidget creation, because the widget
2202  // does not exists yet until QgsAttributeFormRelationEditorWidget is created and the setters
2203  // below directly alter the widget and check for it.
2204  rww->setWidgetConfig( relDef->relationEditorConfiguration() );
2205  rww->setNmRelationId( relDef->nmRelationId() );
2207 
2208  mWidgets.append( rww );
2209  mFormWidgets.append( formWidget );
2210 
2211  newWidgetInfo.widget = formWidget;
2212  newWidgetInfo.showLabel = relDef->showLabel();
2213  newWidgetInfo.labelText = relDef->label();
2214  if ( newWidgetInfo.labelText.isEmpty() )
2215  newWidgetInfo.labelText = rww->relation().name();
2216  newWidgetInfo.labelOnTop = true;
2217  break;
2218  }
2219 
2221  {
2222  const QgsAttributeEditorContainer *container = dynamic_cast<const QgsAttributeEditorContainer *>( widgetDef );
2223  if ( !container )
2224  break;
2225 
2226  int columnCount = container->columnCount();
2227 
2228  if ( columnCount <= 0 )
2229  columnCount = 1;
2230 
2231  QString widgetName;
2232  QWidget *myContainer = nullptr;
2233  if ( container->isGroupBox() )
2234  {
2235  QGroupBox *groupBox = new QGroupBox( parent );
2236  widgetName = QStringLiteral( "QGroupBox" );
2237  if ( container->showLabel() )
2238  groupBox->setTitle( container->name() );
2239  myContainer = groupBox;
2240  newWidgetInfo.widget = myContainer;
2241  }
2242  else
2243  {
2244  myContainer = new QWidget();
2245 
2246  QgsScrollArea *scrollArea = new QgsScrollArea( parent );
2247 
2248  scrollArea->setWidget( myContainer );
2249  scrollArea->setWidgetResizable( true );
2250  scrollArea->setFrameShape( QFrame::NoFrame );
2251  widgetName = QStringLiteral( "QScrollArea QWidget" );
2252 
2253  newWidgetInfo.widget = scrollArea;
2254  }
2255 
2256  if ( container->backgroundColor().isValid() )
2257  {
2258  QString style {QStringLiteral( "background-color: %1;" ).arg( container->backgroundColor().name() )};
2259  newWidgetInfo.widget->setStyleSheet( style );
2260  }
2261 
2262  QGridLayout *gbLayout = new QGridLayout();
2263  myContainer->setLayout( gbLayout );
2264 
2265  int row = 0;
2266  int column = 0;
2267  bool addSpacer = true;
2268 
2269  const QList<QgsAttributeEditorElement *> children = container->children();
2270 
2271  for ( QgsAttributeEditorElement *childDef : children )
2272  {
2273  WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2274 
2275  if ( childDef->type() == QgsAttributeEditorElement::AeTypeContainer )
2276  {
2277  QgsAttributeEditorContainer *containerDef = static_cast<QgsAttributeEditorContainer *>( childDef );
2278  if ( containerDef->visibilityExpression().enabled() )
2279  {
2280  registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().data() ) );
2281  }
2282  }
2283 
2284  if ( widgetInfo.labelText.isNull() || ! widgetInfo.showLabel )
2285  {
2286  gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2287  column += 2;
2288  }
2289  else
2290  {
2291  QLabel *mypLabel = new QLabel( widgetInfo.labelText );
2292 
2293  // Alias DD overrides
2294  if ( childDef->type() == QgsAttributeEditorElement::AeTypeField )
2295  {
2296  const QgsAttributeEditorField *fieldDef { static_cast<QgsAttributeEditorField *>( childDef ) };
2297  const QgsFields fields = vl->fields();
2298  const int fldIdx = fieldDef->idx();
2299  if ( fldIdx < fields.count() && fldIdx >= 0 )
2300  {
2301  const QString fieldName { fields.at( fldIdx ).name() };
2302  if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
2303  {
2304  const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
2305  if ( property.isActive() && ! property.expressionString().isEmpty() )
2306  {
2307  mLabelDataDefinedProperties[ mypLabel ] = property;
2308  }
2309  }
2310  }
2311  }
2312 
2313  mypLabel->setToolTip( widgetInfo.toolTip );
2314  if ( columnCount > 1 && !widgetInfo.labelOnTop )
2315  {
2316  mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2317  }
2318 
2319  mypLabel->setBuddy( widgetInfo.widget );
2320 
2321  if ( widgetInfo.labelOnTop )
2322  {
2323  QVBoxLayout *c = new QVBoxLayout();
2324  mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2325  c->layout()->addWidget( mypLabel );
2326  c->layout()->addWidget( widgetInfo.widget );
2327  gbLayout->addLayout( c, row, column, 1, 2 );
2328  column += 2;
2329  }
2330  else
2331  {
2332  gbLayout->addWidget( mypLabel, row, column++ );
2333  gbLayout->addWidget( widgetInfo.widget, row, column++ );
2334  }
2335  }
2336 
2337  if ( column >= columnCount * 2 )
2338  {
2339  column = 0;
2340  row += 1;
2341  }
2342 
2343  if ( widgetInfo.widget
2344  && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Fixed
2345  && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Maximum
2346  && widgetInfo.widget->sizePolicy().verticalPolicy() != QSizePolicy::Preferred )
2347  addSpacer = false;
2348 
2349  // we consider all relation editors should be expanding
2350  if ( qobject_cast<QgsAttributeFormRelationEditorWidget *>( widgetInfo.widget ) )
2351  addSpacer = false;
2352  }
2353 
2354  if ( addSpacer )
2355  {
2356  QWidget *spacer = new QWidget();
2357  spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
2358  gbLayout->addWidget( spacer, ++row, 0 );
2359  gbLayout->setRowStretch( row, 1 );
2360  }
2361 
2362  newWidgetInfo.labelText = QString();
2363  newWidgetInfo.labelOnTop = true;
2364  newWidgetInfo.showLabel = widgetDef->showLabel();
2365  break;
2366  }
2367 
2369  {
2370  const QgsAttributeEditorQmlElement *elementDef = static_cast<const QgsAttributeEditorQmlElement *>( widgetDef );
2371 
2372  QgsQmlWidgetWrapper *qmlWrapper = new QgsQmlWidgetWrapper( mLayer, nullptr, this );
2373  qmlWrapper->setQmlCode( elementDef->qmlCode() );
2374  context.setAttributeFormMode( mMode );
2375  qmlWrapper->setContext( context );
2376 
2377  mWidgets.append( qmlWrapper );
2378 
2379  newWidgetInfo.widget = qmlWrapper->widget();
2380  newWidgetInfo.labelText = elementDef->name();
2381  newWidgetInfo.labelOnTop = true;
2382  newWidgetInfo.showLabel = widgetDef->showLabel();
2383  break;
2384  }
2385 
2387  {
2388  const QgsAttributeEditorHtmlElement *elementDef = static_cast<const QgsAttributeEditorHtmlElement *>( widgetDef );
2389 
2390  QgsHtmlWidgetWrapper *htmlWrapper = new QgsHtmlWidgetWrapper( mLayer, nullptr, this );
2391  context.setAttributeFormMode( mMode );
2392  htmlWrapper->setHtmlCode( elementDef->htmlCode() );
2393  htmlWrapper->reinitWidget();
2394  mWidgets.append( htmlWrapper );
2395 
2396  newWidgetInfo.widget = htmlWrapper->widget();
2397  newWidgetInfo.labelText = elementDef->name();
2398  newWidgetInfo.labelOnTop = true;
2399  newWidgetInfo.showLabel = widgetDef->showLabel();
2400  mNeedsGeometry |= htmlWrapper->needsGeometry();
2401  break;
2402  }
2403 
2404  default:
2405  QgsDebugMsg( QStringLiteral( "Unknown attribute editor widget type encountered..." ) );
2406  break;
2407  }
2408 
2409  return newWidgetInfo;
2410 }
2411 
2412 void QgsAttributeForm::addWidgetWrapper( QgsEditorWidgetWrapper *eww )
2413 {
2414  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
2415  {
2416  QgsEditorWidgetWrapper *meww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2417  if ( meww )
2418  {
2419  // if another widget wrapper exists for the same field
2420  // synchronise them
2421  if ( meww->field() == eww->field() )
2422  {
2425  break;
2426  }
2427  }
2428  }
2429 
2430  mWidgets.append( eww );
2431 }
2432 
2433 void QgsAttributeForm::createWrappers()
2434 {
2435  QList<QWidget *> myWidgets = findChildren<QWidget *>();
2436  const QList<QgsField> fields = mLayer->fields().toList();
2437 
2438  const auto constMyWidgets = myWidgets;
2439  for ( QWidget *myWidget : constMyWidgets )
2440  {
2441  // Check the widget's properties for a relation definition
2442  QVariant vRel = myWidget->property( "qgisRelation" );
2443  if ( vRel.isValid() )
2444  {
2446  QgsRelation relation = relMgr->relation( vRel.toString() );
2447  if ( relation.isValid() )
2448  {
2449  QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
2450  rww->setConfig( mLayer->editFormConfig().widgetConfig( relation.id() ) );
2451  rww->setContext( mContext );
2452  rww->widget(); // Will initialize the widget
2453  mWidgets.append( rww );
2454  }
2455  }
2456  else
2457  {
2458  const auto constFields = fields;
2459  for ( const QgsField &field : constFields )
2460  {
2461  if ( field.name() == myWidget->objectName() )
2462  {
2463  int idx = mLayer->fields().lookupField( field.name() );
2464 
2465  QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( mLayer, idx, myWidget, this, mContext );
2466  addWidgetWrapper( eww );
2467  }
2468  }
2469  }
2470  }
2471 }
2472 
2473 void QgsAttributeForm::afterWidgetInit()
2474 {
2475  bool isFirstEww = true;
2476 
2477  const auto constMWidgets = mWidgets;
2478  for ( QgsWidgetWrapper *ww : constMWidgets )
2479  {
2480  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2481 
2482  if ( eww )
2483  {
2484  if ( isFirstEww )
2485  {
2486  setFocusProxy( eww->widget() );
2487  isFirstEww = false;
2488  }
2489 
2490  connect( eww, &QgsEditorWidgetWrapper::valuesChanged, this, &QgsAttributeForm::onAttributeChanged, Qt::UniqueConnection );
2491  connect( eww, &QgsEditorWidgetWrapper::constraintStatusChanged, this, &QgsAttributeForm::onConstraintStatusChanged, Qt::UniqueConnection );
2492  }
2493  else
2494  {
2495  QgsRelationWidgetWrapper *relationWidgetWrapper = qobject_cast<QgsRelationWidgetWrapper *>( ww );
2496  if ( relationWidgetWrapper )
2497  {
2498  connect( relationWidgetWrapper, &QgsRelationWidgetWrapper::relatedFeaturesChanged, this, &QgsAttributeForm::onRelatedFeaturesChanged, static_cast<Qt::ConnectionType>( Qt::UniqueConnection | Qt::QueuedConnection ) );
2499  }
2500  }
2501  }
2502 }
2503 
2504 
2505 bool QgsAttributeForm::eventFilter( QObject *object, QEvent *e )
2506 {
2507  Q_UNUSED( object )
2508 
2509  if ( e->type() == QEvent::KeyPress )
2510  {
2511  QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( e );
2512  if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
2513  {
2514  // Re-emit to this form so it will be forwarded to parent
2515  event( e );
2516  return true;
2517  }
2518  }
2519 
2520  return false;
2521 }
2522 
2523 void QgsAttributeForm::scanForEqualAttributes( QgsFeatureIterator &fit,
2524  QSet< int > &mixedValueFields,
2525  QHash< int, QVariant > &fieldSharedValues ) const
2526 {
2527  mixedValueFields.clear();
2528  fieldSharedValues.clear();
2529 
2530  QgsFeature f;
2531  bool first = true;
2532  while ( fit.nextFeature( f ) )
2533  {
2534  for ( int i = 0; i < mLayer->fields().count(); ++i )
2535  {
2536  if ( mixedValueFields.contains( i ) )
2537  continue;
2538 
2539  if ( first )
2540  {
2541  fieldSharedValues[i] = f.attribute( i );
2542  }
2543  else
2544  {
2545  if ( fieldSharedValues.value( i ) != f.attribute( i ) )
2546  {
2547  fieldSharedValues.remove( i );
2548  mixedValueFields.insert( i );
2549  }
2550  }
2551  }
2552  first = false;
2553 
2554  if ( mixedValueFields.count() == mLayer->fields().count() )
2555  {
2556  // all attributes are mixed, no need to keep scanning
2557  break;
2558  }
2559  }
2560 }
2561 
2562 
2563 void QgsAttributeForm::layerSelectionChanged()
2564 {
2565  switch ( mMode )
2566  {
2573  break;
2574 
2576  resetMultiEdit( true );
2577  break;
2578  }
2579 }
2580 
2582 {
2583  mIsSettingMultiEditFeatures = true;
2584  mMultiEditFeatureIds = fids;
2585 
2586  if ( fids.isEmpty() )
2587  {
2588  // no selected features
2589  QMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
2590  for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
2591  {
2592  wIt.value()->initialize( QVariant() );
2593  }
2594  mIsSettingMultiEditFeatures = false;
2595  return;
2596  }
2597 
2598  QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFids( fids ) );
2599 
2600  // Scan through all features to determine which attributes are initially the same
2601  QSet< int > mixedValueFields;
2602  QHash< int, QVariant > fieldSharedValues;
2603  scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
2604 
2605  // also fetch just first feature
2606  fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFid( *fids.constBegin() ) );
2607  QgsFeature firstFeature;
2608  fit.nextFeature( firstFeature );
2609 
2610  const auto constMixedValueFields = mixedValueFields;
2611  for ( int fieldIndex : std::as_const( mixedValueFields ) )
2612  {
2613  if ( QgsAttributeFormEditorWidget *w = mFormEditorWidgets.value( fieldIndex, nullptr ) )
2614  {
2615  const QStringList additionalFields = w->editorWidget()->additionalFields();
2616  QVariantList additionalFieldValues;
2617  for ( const QString &additionalField : additionalFields )
2618  additionalFieldValues << firstFeature.attribute( additionalField );
2619  w->initialize( firstFeature.attribute( fieldIndex ), true, additionalFieldValues );
2620  }
2621  }
2622  QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
2623  for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
2624  {
2625  if ( QgsAttributeFormEditorWidget *w = mFormEditorWidgets.value( sharedValueIt.key(), nullptr ) )
2626  {
2627  bool mixed = false;
2628  const QStringList additionalFields = w->editorWidget()->additionalFields();
2629  for ( const QString &additionalField : additionalFields )
2630  {
2631  int index = mLayer->fields().indexFromName( additionalField );
2632  if ( constMixedValueFields.contains( index ) )
2633  {
2634  // if additional field are mixed, it is considered as mixed
2635  mixed = true;
2636  break;
2637  }
2638  }
2639  QVariantList additionalFieldValues;
2640  if ( mixed )
2641  {
2642  for ( const QString &additionalField : additionalFields )
2643  additionalFieldValues << firstFeature.attribute( additionalField );
2644  w->initialize( firstFeature.attribute( sharedValueIt.key() ), true, additionalFieldValues );
2645  }
2646  else
2647  {
2648  for ( const QString &additionalField : additionalFields )
2649  {
2650  int index = mLayer->fields().indexFromName( additionalField );
2651  Q_ASSERT( fieldSharedValues.contains( index ) );
2652  additionalFieldValues << fieldSharedValues.value( index );
2653  }
2654  w->initialize( sharedValueIt.value(), false, additionalFieldValues );
2655  }
2656  }
2657  }
2658 
2659  setMultiEditFeatureIdsRelations( fids );
2660 
2661  mIsSettingMultiEditFeatures = false;
2662 }
2663 
2665 {
2666  if ( mOwnsMessageBar )
2667  delete mMessageBar;
2668  mOwnsMessageBar = false;
2669  mMessageBar = messageBar;
2670 }
2671 
2673 {
2675  {
2676  Q_ASSERT( false );
2677  }
2678 
2679  QStringList filters;
2680  for ( QgsAttributeFormWidget *widget : mFormWidgets )
2681  {
2682  QString filter = widget->currentFilterExpression();
2683  if ( !filter.isNull() )
2684  filters << '(' + filter + ')';
2685  }
2686 
2687  return filters.join( QLatin1String( " AND " ) );
2688 }
2689 
2691 {
2692  mExtraContextScope.reset( extraScope );
2693 }
2694 
2695 void QgsAttributeForm::ContainerInformation::apply( QgsExpressionContext *expressionContext )
2696 {
2697  bool newVisibility = expression.evaluate( expressionContext ).toBool();
2698 
2699  if ( newVisibility != isVisible )
2700  {
2701  if ( tabWidget )
2702  {
2703  tabWidget->setTabVisible( widget, newVisibility );
2704  }
2705  else
2706  {
2707  widget->setVisible( newVisibility );
2708  }
2709 
2710  isVisible = newVisibility;
2711  }
2712 }
2713 
2714 void QgsAttributeForm::updateJoinedFields( const QgsEditorWidgetWrapper &eww )
2715 {
2716  if ( !eww.layer()->fields().exists( eww.fieldIdx() ) )
2717  return;
2718 
2719  QgsFeature formFeature;
2720  QgsField field = eww.layer()->fields().field( eww.fieldIdx() );
2721  QList<const QgsVectorLayerJoinInfo *> infos = eww.layer()->joinBuffer()->joinsWhereFieldIsId( field );
2722 
2723  if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
2724  return;
2725 
2726  const QString hint = tr( "No feature joined" );
2727  const auto constInfos = infos;
2728  for ( const QgsVectorLayerJoinInfo *info : constInfos )
2729  {
2730  if ( !info->isDynamicFormEnabled() )
2731  continue;
2732 
2733  QgsFeature joinFeature = mLayer->joinBuffer()->joinedFeatureOf( info, formFeature );
2734 
2735  mJoinedFeatures[info] = joinFeature;
2736 
2737  if ( info->hasSubset() )
2738  {
2739  const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( *info );
2740 
2741  const auto constSubsetNames = subsetNames;
2742  for ( const QString &field : constSubsetNames )
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 );
2751  hintText.clear();
2752  }
2753 
2754  changeAttribute( prefixedName, val, hintText );
2755  }
2756  }
2757  else
2758  {
2759  const QgsFields joinFields = joinFeature.fields();
2760  for ( const QgsField &field : joinFields )
2761  {
2762  QString prefixedName = info->prefixedFieldName( field );
2763  QVariant val;
2764  QString hintText = hint;
2765 
2766  if ( joinFeature.isValid() )
2767  {
2768  val = joinFeature.attribute( field.name() );
2769  hintText.clear();
2770  }
2771 
2772  changeAttribute( prefixedName, val, hintText );
2773  }
2774  }
2775  }
2776 }
2777 
2778 bool QgsAttributeForm::fieldIsEditable( int fieldIndex ) const
2779 {
2780  return QgsVectorLayerUtils::fieldIsEditable( mLayer, fieldIndex, mFeature );
2781 }
2782 
2783 void QgsAttributeForm::updateFieldDependencies()
2784 {
2785  mDefaultValueDependencies.clear();
2786  mVirtualFieldsDependencies.clear();
2787  mRelatedLayerFieldsDependencies.clear();
2788 
2789  //create defaultValueDependencies
2790  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
2791  {
2792  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2793  if ( ! eww )
2794  continue;
2795 
2796  updateFieldDependenciesDefaultValue( eww );
2797  updateFieldDependenciesVirtualFields( eww );
2798  updateRelatedLayerFieldsDependencies( eww );
2799  }
2800 }
2801 
2802 void QgsAttributeForm::updateFieldDependenciesDefaultValue( QgsEditorWidgetWrapper *eww )
2803 {
2805  const QSet<QString> referencedColumns = exp.referencedColumns();
2806  for ( const QString &referencedColumn : referencedColumns )
2807  {
2808  if ( referencedColumn == QgsFeatureRequest::ALL_ATTRIBUTES )
2809  {
2810  const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
2811 
2812  for ( const int id : allAttributeIds )
2813  {
2814  mDefaultValueDependencies.insertMulti( id, eww );
2815  }
2816  }
2817  else
2818  {
2819  mDefaultValueDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
2820  }
2821  }
2822 }
2823 
2824 void QgsAttributeForm::updateFieldDependenciesVirtualFields( QgsEditorWidgetWrapper *eww )
2825 {
2826  QString expressionField = eww->layer()->expressionField( eww->fieldIdx() );
2827  if ( expressionField.isEmpty() )
2828  return;
2829 
2830  QgsExpression exp( expressionField );
2831  const QSet<QString> referencedColumns = exp.referencedColumns();
2832  for ( const QString &referencedColumn : referencedColumns )
2833  {
2834  if ( referencedColumn == QgsFeatureRequest::ALL_ATTRIBUTES )
2835  {
2836  const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
2837  for ( const int id : allAttributeIds )
2838  {
2839  mVirtualFieldsDependencies.insertMulti( id, eww );
2840  }
2841  }
2842  else
2843  {
2844  mVirtualFieldsDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
2845  }
2846  }
2847 }
2848 
2849 void QgsAttributeForm::updateRelatedLayerFieldsDependencies( QgsEditorWidgetWrapper *eww )
2850 {
2851  if ( eww )
2852  {
2853  QString expressionField = eww->layer()->expressionField( eww->fieldIdx() );
2854  if ( expressionField.contains( QStringLiteral( "relation_aggregate" ) )
2855  || expressionField.contains( QStringLiteral( "get_features" ) ) )
2856  mRelatedLayerFieldsDependencies.insert( eww );
2857  }
2858  else
2859  {
2860  mRelatedLayerFieldsDependencies.clear();
2861  //create defaultValueDependencies
2862  for ( QgsWidgetWrapper *ww : std::as_const( mWidgets ) )
2863  {
2864  QgsEditorWidgetWrapper *editorWidgetWrapper = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2865  if ( ! editorWidgetWrapper )
2866  continue;
2867 
2868  updateRelatedLayerFieldsDependencies( editorWidgetWrapper );
2869  }
2870  }
2871 }
2872 
2873 void QgsAttributeForm::setMultiEditFeatureIdsRelations( const QgsFeatureIds &fids )
2874 {
2875  for ( QgsAttributeFormWidget *formWidget : mFormWidgets )
2876  {
2877  QgsAttributeFormRelationEditorWidget *relationEditorWidget = dynamic_cast<QgsAttributeFormRelationEditorWidget *>( formWidget );
2878  if ( !relationEditorWidget )
2879  continue;
2880 
2881  relationEditorWidget->setMultiEditFeatureIds( fids );
2882  }
2883 }
2884 
2885 void QgsAttributeForm::updateIcon( QgsEditorWidgetWrapper *eww )
2886 {
2887  if ( !eww->widget() || !mIconMap[eww->widget()] )
2888  return;
2889 
2890  // no icon by default
2891  mIconMap[eww->widget()]->hide();
2892 
2893  if ( !eww->widget()->isEnabled() && mLayer->isEditable() )
2894  {
2895  if ( mLayer->fields().fieldOrigin( eww->fieldIdx() ) == QgsFields::OriginJoin )
2896  {
2897  int srcFieldIndex;
2898  const QgsVectorLayerJoinInfo *info = mLayer->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), mLayer->fields(), srcFieldIndex );
2899 
2900  if ( !info )
2901  return;
2902 
2903  if ( !info->isEditable() )
2904  {
2905  const QString file = QStringLiteral( "/mIconJoinNotEditable.svg" );
2906  const QString tooltip = tr( "Join settings do not allow editing" );
2907  reloadIcon( file, tooltip, mIconMap[eww->widget()] );
2908  }
2909  else if ( mMode == QgsAttributeEditorContext::AddFeatureMode && !info->hasUpsertOnEdit() )
2910  {
2911  const QString file = QStringLiteral( "mIconJoinHasNotUpsertOnEdit.svg" );
2912  const QString tooltip = tr( "Join settings do not allow upsert on edit" );
2913  reloadIcon( file, tooltip, mIconMap[eww->widget()] );
2914  }
2915  else if ( !info->joinLayer()->isEditable() )
2916  {
2917  const QString file = QStringLiteral( "/mIconJoinedLayerNotEditable.svg" );
2918  const QString tooltip = tr( "Joined layer is not toggled editable" );
2919  reloadIcon( file, tooltip, mIconMap[eww->widget()] );
2920  }
2921  }
2922  }
2923 }
2924 
2925 void QgsAttributeForm::reloadIcon( const QString &file, const QString &tooltip, QSvgWidget *sw )
2926 {
2927  sw->load( QgsApplication::iconPath( file ) );
2928  sw->setToolTip( tooltip );
2929  sw->show();
2930 }
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:376
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
@ OriginJoin
Field comes from a joined layer (originIndex / 1000 = index of the join, originIndex % 1000 = index w...
Definition: qgsfields.h:52
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
Definition: qgsfields.cpp:189
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:168
bool exists(int i) const
Returns if a field index is valid.
Definition: qgsfields.cpp:153
int size() const
Returns number of items.
Definition: qgsfields.cpp:138
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
Definition: qgsgui.cpp: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:470
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:266
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:2065
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:2064
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