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