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