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