QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 "qgsproject.h"
24 #include "qgspythonrunner.h"
26 #include "qgsvectordataprovider.h"
28 #include "qgsmessagebar.h"
29 #include "qgsmessagebaritem.h"
31 #include "qgseditorwidgetwrapper.h"
32 #include "qgsrelationmanager.h"
33 #include "qgslogger.h"
34 #include "qgstabwidget.h"
35 #include "qgssettings.h"
36 #include "qgsscrollarea.h"
37 #include "qgsgui.h"
39 #include "qgsvectorlayerutils.h"
40 #include "qgsqmlwidgetwrapper.h"
41 #include "qgshtmlwidgetwrapper.h"
42 #include "qgsapplication.h"
44 
45 #include <QDir>
46 #include <QTextStream>
47 #include <QFileInfo>
48 #include <QFile>
49 #include <QFormLayout>
50 #include <QGridLayout>
51 #include <QGroupBox>
52 #include <QKeyEvent>
53 #include <QLabel>
54 #include <QPushButton>
55 #include <QUiLoader>
56 #include <QMessageBox>
57 #include <QToolButton>
58 #include <QMenu>
59 
60 int QgsAttributeForm::sFormCounter = 0;
61 
62 QgsAttributeForm::QgsAttributeForm( QgsVectorLayer *vl, const QgsFeature &feature, const QgsAttributeEditorContext &context, QWidget *parent )
63  : QWidget( parent )
64  , mLayer( vl )
65  , mOwnsMessageBar( true )
66  , mContext( context )
67  , mFormNr( sFormCounter++ )
68  , mIsSaving( false )
69  , mPreventFeatureRefresh( false )
70  , mIsSettingMultiEditFeatures( false )
71  , mUnsavedMultiEditChanges( false )
72  , mEditCommandMessage( tr( "Attributes changed" ) )
73  , mMode( QgsAttributeEditorContext::SingleEditMode )
74 {
75  init();
76  initPython();
77  setFeature( feature );
78 
79  connect( vl, &QgsVectorLayer::updatedFields, this, &QgsAttributeForm::onUpdatedFields );
80  connect( vl, &QgsVectorLayer::beforeAddingExpressionField, this, &QgsAttributeForm::preventFeatureRefresh );
81  connect( vl, &QgsVectorLayer::beforeRemovingExpressionField, this, &QgsAttributeForm::preventFeatureRefresh );
82  connect( vl, &QgsVectorLayer::selectionChanged, this, &QgsAttributeForm::layerSelectionChanged );
83  connect( this, &QgsAttributeForm::modeChanged, this, &QgsAttributeForm::updateContainersVisibility );
84 
85  updateContainersVisibility();
86 }
87 
89 {
90  cleanPython();
91  qDeleteAll( mInterfaces );
92 }
93 
95 {
96  mButtonBox->hide();
97 
98  // Make sure that changes are taken into account if somebody tries to figure out if there have been some
101 }
102 
104 {
105  mButtonBox->show();
106 
107  disconnect( mLayer, &QgsVectorLayer::beforeModifiedCheck, this, &QgsAttributeForm::save );
108 }
109 
111 {
112  disconnect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
113  disconnect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
114 }
115 
117 {
118  mInterfaces.append( iface );
119 }
120 
122 {
123  return mFeature.isValid() && mLayer->isEditable();
124 }
125 
127 {
128  if ( mode == mMode )
129  return;
130 
132  {
133  //switching out of multi edit mode triggers a save
134  if ( mUnsavedMultiEditChanges )
135  {
136  // prompt for save
137  int res = QMessageBox::question( this, tr( "Multiedit Attributes" ),
138  tr( "Apply changes to edited features?" ), QMessageBox::Yes | QMessageBox::No );
139  if ( res == QMessageBox::Yes )
140  {
141  save();
142  }
143  }
144  clearMultiEditMessages();
145  }
146  mUnsavedMultiEditChanges = false;
147 
148  mMode = mode;
149 
150  if ( mButtonBox->isVisible() && mMode == QgsAttributeEditorContext::SingleEditMode )
151  {
153  }
154  else
155  {
156  disconnect( mLayer, &QgsVectorLayer::beforeModifiedCheck, this, &QgsAttributeForm::save );
157  }
158 
159  //update all form editor widget modes to match
160  for ( QgsAttributeFormWidget *w : qgis::as_const( mFormWidgets ) )
161  {
162  switch ( mode )
163  {
166  break;
167 
170  break;
171 
174  break;
175 
178  break;
179 
182  break;
183 
186  break;
187  }
188  }
189  //update all form editor widget modes to match
190  for ( QgsWidgetWrapper *w : qgis::as_const( mWidgets ) )
191  {
192  QgsAttributeEditorContext newContext = w->context();
193  newContext.setAttributeFormMode( mMode );
194  w->setContext( newContext );
195  }
196 
197  bool relationWidgetsVisible = ( mMode != QgsAttributeEditorContext::MultiEditMode && mMode != QgsAttributeEditorContext::AggregateSearchMode );
198  for ( QgsAttributeFormRelationEditorWidget *w : findChildren< QgsAttributeFormRelationEditorWidget * >() )
199  {
200  w->setVisible( relationWidgetsVisible );
201  }
202 
203  switch ( mode )
204  {
206  setFeature( mFeature );
207  mSearchButtonBox->setVisible( false );
208  break;
209 
211  synchronizeEnabledState();
212  mSearchButtonBox->setVisible( false );
213  break;
214 
216  resetMultiEdit( false );
217  synchronizeEnabledState();
218  mSearchButtonBox->setVisible( false );
219  break;
220 
222  mSearchButtonBox->setVisible( true );
223  hideButtonBox();
224  break;
225 
227  mSearchButtonBox->setVisible( false );
228  hideButtonBox();
229  break;
230 
232  setFeature( mFeature );
233  mSearchButtonBox->setVisible( false );
234  break;
235  }
236 
237  emit modeChanged( mMode );
238 }
239 
240 void QgsAttributeForm::changeAttribute( const QString &field, const QVariant &value, const QString &hintText )
241 {
242  const auto constMWidgets = mWidgets;
243  for ( QgsWidgetWrapper *ww : constMWidgets )
244  {
245  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
246  if ( eww && eww->field().name() == field )
247  {
248  eww->setValue( value );
249  eww->setHint( hintText );
250  }
251  }
252 }
253 
255 {
256  mIsSettingFeature = true;
257  mFeature = feature;
258 
259  switch ( mMode )
260  {
264  {
265  resetValues();
266 
267  synchronizeEnabledState();
268 
269  const auto constMInterfaces = mInterfaces;
270  for ( QgsAttributeFormInterface *iface : constMInterfaces )
271  {
272  iface->featureChanged();
273  }
274  break;
275  }
278  {
279  resetValues();
280  break;
281  }
283  {
284  //ignore setFeature
285  break;
286  }
287  }
288  mIsSettingFeature = false;
289 }
290 
291 bool QgsAttributeForm::saveEdits()
292 {
293  bool success = true;
294  bool changedLayer = false;
295 
296  QgsFeature updatedFeature = QgsFeature( mFeature );
297 
298  if ( mFeature.isValid() || mMode == QgsAttributeEditorContext::AddFeatureMode )
299  {
300  bool doUpdate = false;
301 
302  // An add dialog should perform an action by default
303  // and not only if attributes have "changed"
305  doUpdate = true;
306 
307  QgsAttributes src = mFeature.attributes();
308  QgsAttributes dst = mFeature.attributes();
309 
310  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
311  {
312  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
313  if ( eww )
314  {
315  QVariant dstVar = dst.at( eww->fieldIdx() );
316  QVariant srcVar = eww->value();
317 
318  // need to check dstVar.isNull() != srcVar.isNull()
319  // otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
320  // be careful- sometimes two null qvariants will be reported as not equal!! (e.g., different types)
321  bool changed = ( dstVar != srcVar && !dstVar.isNull() && !srcVar.isNull() )
322  || ( dstVar.isNull() != srcVar.isNull() );
323  if ( changed && srcVar.isValid() && fieldIsEditable( eww->fieldIdx() ) )
324  {
325  dst[eww->fieldIdx()] = srcVar;
326 
327  doUpdate = true;
328  }
329  }
330  }
331 
332  updatedFeature.setAttributes( dst );
333 
334  const auto constMInterfaces = mInterfaces;
335  for ( QgsAttributeFormInterface *iface : constMInterfaces )
336  {
337  if ( !iface->acceptChanges( updatedFeature ) )
338  {
339  doUpdate = false;
340  }
341  }
342 
343  if ( doUpdate )
344  {
346  {
347  mFeature.setValid( true );
348  mLayer->beginEditCommand( mEditCommandMessage );
349  bool res = mLayer->addFeature( updatedFeature );
350  if ( res )
351  {
352  mFeature.setAttributes( updatedFeature.attributes() );
353  mLayer->endEditCommand();
355  changedLayer = true;
356  }
357  else
358  mLayer->destroyEditCommand();
359  }
360  else
361  {
362  mLayer->beginEditCommand( mEditCommandMessage );
363 
364  QgsAttributeMap newValues;
365  QgsAttributeMap oldValues;
366 
367  int n = 0;
368  for ( int i = 0; i < dst.count(); ++i )
369  {
370  if ( qgsVariantEqual( dst.at( i ), src.at( i ) ) // If field is not changed...
371  || !dst.at( i ).isValid() // or the widget returns invalid (== do not change)
372  || !fieldIsEditable( i ) ) // or the field cannot be edited ...
373  {
374  continue;
375  }
376 
377  QgsDebugMsg( QStringLiteral( "Updating field %1" ).arg( i ) );
378  QgsDebugMsg( QStringLiteral( "dst:'%1' (type:%2, isNull:%3, isValid:%4)" )
379  .arg( dst.at( i ).toString(), dst.at( i ).typeName() ).arg( dst.at( i ).isNull() ).arg( dst.at( i ).isValid() ) );
380  QgsDebugMsg( QStringLiteral( "src:'%1' (type:%2, isNull:%3, isValid:%4)" )
381  .arg( src.at( i ).toString(), src.at( i ).typeName() ).arg( src.at( i ).isNull() ).arg( src.at( i ).isValid() ) );
382 
383  newValues[i] = dst.at( i );
384  oldValues[i] = src.at( i );
385 
386  n++;
387  }
388 
389  success = mLayer->changeAttributeValues( mFeature.id(), newValues, oldValues );
390 
391  if ( success && n > 0 )
392  {
393  mLayer->endEditCommand();
394  mFeature.setAttributes( dst );
395  changedLayer = true;
396  }
397  else
398  {
399  mLayer->destroyEditCommand();
400  }
401  }
402  }
403  }
404 
405  emit featureSaved( updatedFeature );
406 
407  // [MD] Refresh canvas only when absolutely necessary - it interferes with other stuff (#11361).
408  // This code should be revisited - and the signals should be fired (+ layer repainted)
409  // only when actually doing any changes. I am unsure if it is actually a good idea
410  // to call save() whenever some code asks for vector layer's modified status
411  // (which is the case when attribute table is open)
412  if ( changedLayer )
413  mLayer->triggerRepaint();
414 
415  return success;
416 }
417 
418 void QgsAttributeForm::resetMultiEdit( bool promptToSave )
419 {
420  if ( promptToSave )
421  save();
422 
423  mUnsavedMultiEditChanges = false;
425 }
426 
427 void QgsAttributeForm::multiEditMessageClicked( const QString &link )
428 {
429  clearMultiEditMessages();
430  resetMultiEdit( link == QLatin1String( "#apply" ) );
431 }
432 
433 void QgsAttributeForm::filterTriggered()
434 {
435  QString filter = createFilterExpression();
436  emit filterExpressionSet( filter, ReplaceFilter );
437  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
439 }
440 
441 void QgsAttributeForm::searchZoomTo()
442 {
443  QString filter = createFilterExpression();
444  if ( filter.isEmpty() )
445  return;
446 
447  emit zoomToFeatures( filter );
448 }
449 
450 void QgsAttributeForm::searchFlash()
451 {
452  QString filter = createFilterExpression();
453  if ( filter.isEmpty() )
454  return;
455 
456  emit flashFeatures( filter );
457 }
458 
459 void QgsAttributeForm::filterAndTriggered()
460 {
461  QString filter = createFilterExpression();
462  if ( filter.isEmpty() )
463  return;
464 
465  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
467  emit filterExpressionSet( filter, FilterAnd );
468 }
469 
470 void QgsAttributeForm::filterOrTriggered()
471 {
472  QString filter = createFilterExpression();
473  if ( filter.isEmpty() )
474  return;
475 
476  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
478  emit filterExpressionSet( filter, FilterOr );
479 }
480 
481 void QgsAttributeForm::pushSelectedFeaturesMessage()
482 {
483  int count = mLayer->selectedFeatureCount();
484  if ( count > 0 )
485  {
486  mMessageBar->pushMessage( QString(),
487  tr( "%n matching feature(s) selected", "matching features", count ),
488  Qgis::Info,
489  messageTimeout() );
490  }
491  else
492  {
493  mMessageBar->pushMessage( QString(),
494  tr( "No matching features found" ),
496  messageTimeout() );
497  }
498 }
499 
500 void QgsAttributeForm::runSearchSelect( QgsVectorLayer::SelectBehavior behavior )
501 {
502  QString filter = createFilterExpression();
503  if ( filter.isEmpty() )
504  return;
505 
506  mLayer->selectByExpression( filter, behavior );
507  pushSelectedFeaturesMessage();
508  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
510 }
511 
512 void QgsAttributeForm::searchSetSelection()
513 {
514  runSearchSelect( QgsVectorLayer::SetSelection );
515 }
516 
517 void QgsAttributeForm::searchAddToSelection()
518 {
519  runSearchSelect( QgsVectorLayer::AddToSelection );
520 }
521 
522 void QgsAttributeForm::searchRemoveFromSelection()
523 {
524  runSearchSelect( QgsVectorLayer::RemoveFromSelection );
525 }
526 
527 void QgsAttributeForm::searchIntersectSelection()
528 {
529  runSearchSelect( QgsVectorLayer::IntersectSelection );
530 }
531 
532 bool QgsAttributeForm::saveMultiEdits()
533 {
534  //find changed attributes
535  QgsAttributeMap newAttributeValues;
536  QMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
537  for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
538  {
539  QgsAttributeFormEditorWidget *w = wIt.value();
540  if ( !w->hasChanged() )
541  continue;
542 
543  if ( !w->currentValue().isValid() // if the widget returns invalid (== do not change)
544  || mLayer->editFormConfig().readOnly( wIt.key() ) ) // or the field cannot be edited ...
545  {
546  continue;
547  }
548 
549  // let editor know we've accepted the changes
550  w->changesCommitted();
551 
552  newAttributeValues.insert( wIt.key(), w->currentValue() );
553  }
554 
555  if ( newAttributeValues.isEmpty() )
556  {
557  //nothing to change
558  return true;
559  }
560 
561 #if 0
562  // prompt for save
563  int res = QMessageBox::information( this, tr( "Multiedit Attributes" ),
564  tr( "Edits will be applied to all selected features." ), QMessageBox::Ok | QMessageBox::Cancel );
565  if ( res != QMessageBox::Ok )
566  {
567  resetMultiEdit();
568  return false;
569  }
570 #endif
571 
572  mLayer->beginEditCommand( tr( "Updated multiple feature attributes" ) );
573 
574  bool success = true;
575 
576  const auto constMMultiEditFeatureIds = mMultiEditFeatureIds;
577  for ( QgsFeatureId fid : constMMultiEditFeatureIds )
578  {
579  QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
580  for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
581  {
582  success &= mLayer->changeAttributeValue( fid, aIt.key(), aIt.value() );
583  }
584  }
585 
586  clearMultiEditMessages();
587  if ( success )
588  {
589  mLayer->endEditCommand();
590  mLayer->triggerRepaint();
591  mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Attribute changes for multiple features applied." ), Qgis::Success, messageTimeout() );
592  }
593  else
594  {
595  mLayer->destroyEditCommand();
596  mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Changes could not be applied." ), Qgis::Warning, messageTimeout() );
597  }
598 
599  if ( !mButtonBox->isVisible() )
600  mMessageBar->pushItem( mMultiEditMessageBarItem );
601  return success;
602 }
603 
605 {
606  if ( mIsSaving )
607  return true;
608 
609  for ( QgsWidgetWrapper *wrapper : qgis::as_const( mWidgets ) )
610  {
611  wrapper->notifyAboutToSave();
612  }
613 
614  // only do the dirty checks when editing an existing feature - for new
615  // features we need to add them even if the attributes are unchanged from the initial
616  // default values
617  switch ( mMode )
618  {
622  if ( !mDirty )
623  return true;
624  break;
625 
629  break;
630  }
631 
632  mIsSaving = true;
633 
634  bool success = true;
635 
636  emit beforeSave( success );
637 
638  // Somebody wants to prevent this form from saving
639  if ( !success )
640  return false;
641 
642  switch ( mMode )
643  {
649  success = saveEdits();
650  break;
651 
653  success = saveMultiEdits();
654  break;
655  }
656 
657  mIsSaving = false;
658  mUnsavedMultiEditChanges = false;
659  mDirty = false;
660 
661  return success;
662 }
663 
665 {
666  mValuesInitialized = false;
667  const auto constMWidgets = mWidgets;
668  for ( QgsWidgetWrapper *ww : constMWidgets )
669  {
670  ww->setFeature( mFeature );
671  }
672  mValuesInitialized = true;
673  mDirty = false;
674 }
675 
677 {
678  const auto widgets { findChildren< QgsAttributeFormEditorWidget * >() };
679  for ( QgsAttributeFormEditorWidget *w : widgets )
680  {
681  w->resetSearch();
682  }
683 }
684 
685 void QgsAttributeForm::clearMultiEditMessages()
686 {
687  if ( mMultiEditUnsavedMessageBarItem )
688  {
689  if ( !mButtonBox->isVisible() )
690  mMessageBar->popWidget( mMultiEditUnsavedMessageBarItem );
691  mMultiEditUnsavedMessageBarItem = nullptr;
692  }
693  if ( mMultiEditMessageBarItem )
694  {
695  if ( !mButtonBox->isVisible() )
696  mMessageBar->popWidget( mMultiEditMessageBarItem );
697  mMultiEditMessageBarItem = nullptr;
698  }
699 }
700 
701 QString QgsAttributeForm::createFilterExpression() const
702 {
703  QStringList filters;
704  for ( QgsAttributeFormWidget *w : qgis::as_const( mFormWidgets ) )
705  {
706  QString filter = w->currentFilterExpression();
707  if ( !filter.isEmpty() )
708  filters << filter;
709  }
710 
711  if ( filters.isEmpty() )
712  return QString();
713 
714  QString filter = filters.join( QStringLiteral( ") AND (" ) ).prepend( '(' ).append( ')' );
715  return filter;
716 }
717 
718 
719 void QgsAttributeForm::onAttributeChanged( const QVariant &value )
720 {
721  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
722  Q_ASSERT( eww );
723 
724  bool signalEmitted = false;
725 
726  if ( mValuesInitialized )
727  mDirty = true;
728 
729  switch ( mMode )
730  {
734  {
736  emit attributeChanged( eww->field().name(), value );
738  emit widgetValueChanged( eww->field().name(), value, !mIsSettingFeature );
739 
740  signalEmitted = true;
741 
742  updateJoinedFields( *eww );
743 
744  break;
745  }
747  {
748  if ( !mIsSettingMultiEditFeatures )
749  {
750  mUnsavedMultiEditChanges = true;
751 
752  QLabel *msgLabel = new QLabel( tr( "Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
753  msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
754  msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
755  connect( msgLabel, &QLabel::linkActivated, this, &QgsAttributeForm::multiEditMessageClicked );
756  clearMultiEditMessages();
757 
758  mMultiEditUnsavedMessageBarItem = new QgsMessageBarItem( msgLabel, Qgis::Warning );
759  if ( !mButtonBox->isVisible() )
760  mMessageBar->pushItem( mMultiEditUnsavedMessageBarItem );
761  }
762  break;
763  }
766  //nothing to do
767  break;
768  }
769 
770  updateConstraints( eww );
771 
772  if ( !signalEmitted )
773  {
775  emit attributeChanged( eww->field().name(), value );
777  emit widgetValueChanged( eww->field().name(), value, !mIsSettingFeature );
778  }
779 }
780 
781 void QgsAttributeForm::updateAllConstraints()
782 {
783  const auto constMWidgets = mWidgets;
784  for ( QgsWidgetWrapper *ww : constMWidgets )
785  {
786  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
787  if ( eww )
788  updateConstraints( eww );
789  }
790 }
791 
792 void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww )
793 {
794  // get the current feature set in the form
795  QgsFeature ft;
796  if ( currentFormFeature( ft ) )
797  {
798  // if the layer is NOT being edited then we only check layer based constraints, and not
799  // any constraints enforced by the provider. Because:
800  // 1. we want to keep browsing features nice and responsive. It's nice to give feedback as to whether
801  // the value checks out, but not if it's too slow to do so. Some constraints (e.g., unique) can be
802  // expensive to test. A user can freely remove a layer-based constraint if it proves to be too slow
803  // to test, but they are unlikely to have any control over provider-side constraints
804  // 2. the provider has already accepted the value, so presumably it doesn't violate the constraint
805  // and there's no point rechecking!
806 
807  // update eww constraint
808  updateConstraint( ft, eww );
809 
810  // update eww dependencies constraint
811  const QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
812 
813  for ( QgsEditorWidgetWrapper *depsEww : deps )
814  updateConstraint( ft, depsEww );
815 
816  // sync OK button status
817  synchronizeEnabledState();
818 
819  mExpressionContext.setFeature( ft );
820 
821  mExpressionContext << QgsExpressionContextUtils::formScope( ft, mContext.attributeFormModeString() );
822 
823  // Recheck visibility for all containers which are controlled by this value
824  const QVector<ContainerInformation *> infos = mContainerInformationDependency.value( eww->field().name() );
825  for ( ContainerInformation *info : infos )
826  {
827  info->apply( &mExpressionContext );
828  }
829  }
830 }
831 
832 void QgsAttributeForm::updateContainersVisibility()
833 {
834  mExpressionContext << QgsExpressionContextUtils::formScope( QgsFeature( mFeature ), mContext.attributeFormModeString() );
835 
836  const QVector<ContainerInformation *> infos = mContainerVisibilityInformation;
837 
838  for ( ContainerInformation *info : infos )
839  {
840  info->apply( &mExpressionContext );
841  }
842 
843  //and update the constraints
844  updateAllConstraints();
845 }
846 
847 void QgsAttributeForm::updateConstraint( const QgsFeature &ft, QgsEditorWidgetWrapper *eww )
848 {
850 
851  if ( eww->layer()->fields().fieldOrigin( eww->fieldIdx() ) == QgsFields::OriginJoin )
852  {
853  int srcFieldIdx;
854  const QgsVectorLayerJoinInfo *info = eww->layer()->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), eww->layer()->fields(), srcFieldIdx );
855 
856  if ( info && info->joinLayer() && info->isDynamicFormEnabled() )
857  {
858  if ( mJoinedFeatures.contains( info ) )
859  {
860  eww->updateConstraint( info->joinLayer(), srcFieldIdx, mJoinedFeatures[info], constraintOrigin );
861  return;
862  }
863  else // if we are here, it means there's not joined field for this feature
864  {
865  eww->updateConstraint( QgsFeature() );
866  return;
867  }
868  }
869  }
870 
871  // default constraint update
872  eww->updateConstraint( ft, constraintOrigin );
873 }
874 
875 bool QgsAttributeForm::currentFormFeature( QgsFeature &feature )
876 {
877  bool rc = true;
878  feature = QgsFeature( mFeature );
879  QgsAttributes dst = feature.attributes();
880 
881  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
882  {
883  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
884 
885  if ( !eww )
886  continue;
887 
888  if ( dst.count() > eww->fieldIdx() )
889  {
890  QVariant dstVar = dst.at( eww->fieldIdx() );
891  QVariant srcVar = eww->value();
892  // need to check dstVar.isNull() != srcVar.isNull()
893  // otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
894  if ( ( dstVar != srcVar || dstVar.isNull() != srcVar.isNull() ) && srcVar.isValid() )
895  dst[eww->fieldIdx()] = srcVar;
896  }
897  else
898  {
899  rc = false;
900  break;
901  }
902  }
903 
904  feature.setAttributes( dst );
905 
906  return rc;
907 }
908 
909 
910 void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
911 {
912  mContainerVisibilityInformation.append( info );
913 
914  const QSet<QString> referencedColumns = info->expression.referencedColumns();
915 
916  for ( const QString &col : referencedColumns )
917  {
918  mContainerInformationDependency[ col ].append( info );
919  }
920 }
921 
922 bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions )
923 {
924  bool valid( true );
925 
926  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
927  {
928  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
929  if ( eww )
930  {
931  if ( ! eww->isValidConstraint() )
932  {
933  invalidFields.append( eww->field().displayName() );
934 
935  descriptions.append( eww->constraintFailureReason() );
936 
937  if ( eww->isBlockingCommit() )
938  valid = false; // continue to get all invalid fields
939  }
940  }
941  }
942 
943  return valid;
944 }
945 
946 void QgsAttributeForm::onAttributeAdded( int idx )
947 {
948  mPreventFeatureRefresh = false;
949  if ( mFeature.isValid() )
950  {
951  QgsAttributes attrs = mFeature.attributes();
952  attrs.insert( idx, QVariant( layer()->fields().at( idx ).type() ) );
953  mFeature.setFields( layer()->fields() );
954  mFeature.setAttributes( attrs );
955  }
956  init();
957  setFeature( mFeature );
958 }
959 
960 void QgsAttributeForm::onAttributeDeleted( int idx )
961 {
962  mPreventFeatureRefresh = false;
963  if ( mFeature.isValid() )
964  {
965  QgsAttributes attrs = mFeature.attributes();
966  attrs.remove( idx );
967  mFeature.setFields( layer()->fields() );
968  mFeature.setAttributes( attrs );
969  }
970  init();
971  setFeature( mFeature );
972 }
973 
974 void QgsAttributeForm::onUpdatedFields()
975 {
976  mPreventFeatureRefresh = false;
977  if ( mFeature.isValid() )
978  {
979  QgsAttributes attrs( layer()->fields().size() );
980  for ( int i = 0; i < layer()->fields().size(); i++ )
981  {
982  int idx = mFeature.fields().indexFromName( layer()->fields().at( i ).name() );
983  if ( idx != -1 )
984  {
985  attrs[i] = mFeature.attributes().at( idx );
986  if ( mFeature.attributes().at( idx ).type() != layer()->fields().at( i ).type() )
987  {
988  attrs[i].convert( layer()->fields().at( i ).type() );
989  }
990  }
991  else
992  {
993  attrs[i] = QVariant( layer()->fields().at( i ).type() );
994  }
995  }
996  mFeature.setFields( layer()->fields() );
997  mFeature.setAttributes( attrs );
998  }
999  init();
1000  setFeature( mFeature );
1001 }
1002 
1003 void QgsAttributeForm::onConstraintStatusChanged( const QString &constraint,
1004  const QString &description, const QString &err, QgsEditorWidgetWrapper::ConstraintResult result )
1005 {
1006  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
1007  Q_ASSERT( eww );
1008 
1009  QgsAttributeFormEditorWidget *formEditorWidget = mFormEditorWidgets.value( eww->fieldIdx() );
1010 
1011  if ( formEditorWidget )
1012  formEditorWidget->setConstraintStatus( constraint, description, err, result );
1013 }
1014 
1015 QList<QgsEditorWidgetWrapper *> QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper *w )
1016 {
1017  QList<QgsEditorWidgetWrapper *> wDeps;
1018  QString name = w->field().name();
1019 
1020  // for each widget in the current form
1021  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
1022  {
1023  // get the wrapper
1024  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1025  if ( eww )
1026  {
1027  // compare name to not compare w to itself
1028  QString ewwName = eww->field().name();
1029  if ( name != ewwName )
1030  {
1031  // get expression and referencedColumns
1032  QgsExpression expr = eww->layer()->fields().at( eww->fieldIdx() ).constraints().constraintExpression();
1033 
1034  const auto referencedColumns = expr.referencedColumns();
1035 
1036  for ( const QString &colName : referencedColumns )
1037  {
1038  if ( name == colName )
1039  {
1040  wDeps.append( eww );
1041  break;
1042  }
1043  }
1044  }
1045  }
1046  }
1047 
1048  return wDeps;
1049 }
1050 
1051 QgsRelationWidgetWrapper *QgsAttributeForm::setupRelationWidgetWrapper( const QgsRelation &rel, const QgsAttributeEditorContext &context )
1052 {
1053  QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( mLayer, rel, nullptr, this );
1054  const QVariantMap config = mLayer->editFormConfig().widgetConfig( rel.id() );
1055  rww->setConfig( config );
1056  rww->setContext( context );
1057 
1058  return rww;
1059 }
1060 
1061 void QgsAttributeForm::preventFeatureRefresh()
1062 {
1063  mPreventFeatureRefresh = true;
1064 }
1065 
1067 {
1068  if ( mPreventFeatureRefresh || mLayer->isEditable() || !mFeature.isValid() )
1069  return;
1070 
1071  // reload feature if layer changed although not editable
1072  // (datasource probably changed bypassing QgsVectorLayer)
1073  if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeature.id() ) ).nextFeature( mFeature ) )
1074  return;
1075 
1076  init();
1077  setFeature( mFeature );
1078 }
1079 
1080 void QgsAttributeForm::synchronizeEnabledState()
1081 {
1082  bool isEditable = ( mFeature.isValid()
1084  || mMode == QgsAttributeEditorContext::MultiEditMode ) && mLayer->isEditable();
1085 
1086  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
1087  {
1088  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1089  if ( eww )
1090  {
1091  QgsAttributeFormEditorWidget *formWidget = mFormEditorWidgets.value( eww->fieldIdx() );
1092 
1093  if ( formWidget )
1094  formWidget->setConstraintResultVisible( isEditable );
1095 
1096  eww->setConstraintResultVisible( isEditable );
1097 
1098  bool enabled = isEditable && fieldIsEditable( eww->fieldIdx() );
1099  ww->setEnabled( enabled );
1100 
1101  updateIcon( eww );
1102  }
1103  }
1104 
1106  {
1107  QStringList invalidFields, descriptions;
1108  bool validConstraint = currentFormValidConstraints( invalidFields, descriptions );
1109 
1110  isEditable = isEditable & validConstraint;
1111  }
1112 
1113  // change OK button status
1114  QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1115  if ( okButton )
1116  okButton->setEnabled( isEditable );
1117 }
1118 
1119 void QgsAttributeForm::init()
1120 {
1121  QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1122 
1123  // Cleanup of any previously shown widget, we start from scratch
1124  QWidget *formWidget = nullptr;
1125 
1126  bool buttonBoxVisible = true;
1127  // Cleanup button box but preserve visibility
1128  if ( mButtonBox )
1129  {
1130  buttonBoxVisible = mButtonBox->isVisible();
1131  delete mButtonBox;
1132  mButtonBox = nullptr;
1133  }
1134 
1135  if ( mSearchButtonBox )
1136  {
1137  delete mSearchButtonBox;
1138  mSearchButtonBox = nullptr;
1139  }
1140 
1141  qDeleteAll( mWidgets );
1142  mWidgets.clear();
1143 
1144  while ( QWidget *w = this->findChild<QWidget *>() )
1145  {
1146  delete w;
1147  }
1148  delete layout();
1149 
1150  QVBoxLayout *vl = new QVBoxLayout();
1151  vl->setMargin( 0 );
1152  vl->setContentsMargins( 0, 0, 0, 0 );
1153  mMessageBar = new QgsMessageBar( this );
1154  mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1155  vl->addWidget( mMessageBar );
1156 
1157  setLayout( vl );
1158 
1159  // Get a layout
1160  QGridLayout *layout = new QGridLayout();
1161  QWidget *container = new QWidget();
1162  container->setLayout( layout );
1163  vl->addWidget( container );
1164 
1165  mFormEditorWidgets.clear();
1166  mFormWidgets.clear();
1167 
1168  // a bar to warn the user with non-blocking messages
1169  setContentsMargins( 0, 0, 0, 0 );
1170 
1171  // Try to load Ui-File for layout
1172  if ( mContext.allowCustomUi() && mLayer->editFormConfig().layout() == QgsEditFormConfig::UiFileLayout &&
1173  !mLayer->editFormConfig().uiForm().isEmpty() )
1174  {
1175  QgsDebugMsg( QStringLiteral( "loading form: %1" ).arg( mLayer->editFormConfig().uiForm() ) );
1176  const QString path = mLayer->editFormConfig().uiForm();
1178  if ( file && file->open( QFile::ReadOnly ) )
1179  {
1180  QUiLoader loader;
1181 
1182  QFileInfo fi( file->fileName() );
1183  loader.setWorkingDirectory( fi.dir() );
1184  formWidget = loader.load( file, this );
1185  if ( formWidget )
1186  {
1187  formWidget->setWindowFlags( Qt::Widget );
1188  layout->addWidget( formWidget );
1189  formWidget->show();
1190  file->close();
1191  mButtonBox = findChild<QDialogButtonBox *>();
1192  createWrappers();
1193 
1194  formWidget->installEventFilter( this );
1195  }
1196  }
1197  }
1198 
1199  QgsTabWidget *tabWidget = nullptr;
1200 
1201  // Tab layout
1202  if ( !formWidget && mLayer->editFormConfig().layout() == QgsEditFormConfig::TabLayout )
1203  {
1204  int row = 0;
1205  int column = 0;
1206  int columnCount = 1;
1207 
1208  const QList<QgsAttributeEditorElement *> tabs = mLayer->editFormConfig().tabs();
1209 
1210  for ( QgsAttributeEditorElement *widgDef : tabs )
1211  {
1212  if ( widgDef->type() == QgsAttributeEditorElement::AeTypeContainer )
1213  {
1214  QgsAttributeEditorContainer *containerDef = dynamic_cast<QgsAttributeEditorContainer *>( widgDef );
1215  if ( !containerDef )
1216  continue;
1217 
1218  if ( containerDef->isGroupBox() )
1219  {
1220  tabWidget = nullptr;
1221  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1222  layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1223  if ( containerDef->visibilityExpression().enabled() )
1224  {
1225  registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().data() ) );
1226  }
1227  column += 2;
1228  }
1229  else
1230  {
1231  if ( !tabWidget )
1232  {
1233  tabWidget = new QgsTabWidget();
1234  layout->addWidget( tabWidget, row, column, 1, 2 );
1235  column += 2;
1236  }
1237 
1238  QWidget *tabPage = new QWidget( tabWidget );
1239 
1240  tabWidget->addTab( tabPage, widgDef->name() );
1241 
1242  if ( containerDef->visibilityExpression().enabled() )
1243  {
1244  registerContainerInformation( new ContainerInformation( tabWidget, tabPage, containerDef->visibilityExpression().data() ) );
1245  }
1246  QGridLayout *tabPageLayout = new QGridLayout();
1247  tabPage->setLayout( tabPageLayout );
1248 
1249  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1250  tabPageLayout->addWidget( widgetInfo.widget );
1251  }
1252  }
1253  else
1254  {
1255  tabWidget = nullptr;
1256  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1257  QLabel *label = new QLabel( widgetInfo.labelText );
1258  label->setToolTip( widgetInfo.toolTip );
1259  if ( columnCount > 1 && !widgetInfo.labelOnTop )
1260  {
1261  label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1262  }
1263 
1264  label->setBuddy( widgetInfo.widget );
1265 
1266  if ( !widgetInfo.showLabel )
1267  {
1268  QVBoxLayout *c = new QVBoxLayout();
1269  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1270  c->addWidget( widgetInfo.widget );
1271  layout->addLayout( c, row, column, 1, 2 );
1272  column += 2;
1273  }
1274  else if ( widgetInfo.labelOnTop )
1275  {
1276  QVBoxLayout *c = new QVBoxLayout();
1277  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1278  c->addWidget( label );
1279  c->addWidget( widgetInfo.widget );
1280  layout->addLayout( c, row, column, 1, 2 );
1281  column += 2;
1282  }
1283  else
1284  {
1285  layout->addWidget( label, row, column++ );
1286  layout->addWidget( widgetInfo.widget, row, column++ );
1287  }
1288  }
1289 
1290  if ( column >= columnCount * 2 )
1291  {
1292  column = 0;
1293  row += 1;
1294  }
1295  }
1296  formWidget = container;
1297  }
1298 
1299  // Autogenerate Layout
1300  // If there is still no layout loaded (defined as autogenerate or other methods failed)
1301  mIconMap.clear();
1302 
1303  if ( !formWidget )
1304  {
1305  formWidget = new QWidget( this );
1306  QGridLayout *gridLayout = new QGridLayout( formWidget );
1307  formWidget->setLayout( gridLayout );
1308 
1309  if ( mContext.formMode() != QgsAttributeEditorContext::Embed )
1310  {
1311  // put the form into a scroll area to nicely handle cases with lots of attributes
1312  QgsScrollArea *scrollArea = new QgsScrollArea( this );
1313  scrollArea->setWidget( formWidget );
1314  scrollArea->setWidgetResizable( true );
1315  scrollArea->setFrameShape( QFrame::NoFrame );
1316  scrollArea->setFrameShadow( QFrame::Plain );
1317  scrollArea->setFocusProxy( this );
1318  layout->addWidget( scrollArea );
1319  }
1320  else
1321  {
1322  layout->addWidget( formWidget );
1323  }
1324 
1325  int row = 0;
1326 
1327  const QgsFields fields = mLayer->fields();
1328 
1329  for ( const QgsField &field : fields )
1330  {
1331  int idx = fields.lookupField( field.name() );
1332  if ( idx < 0 )
1333  continue;
1334 
1335  //show attribute alias if available
1336  QString fieldName = mLayer->attributeDisplayName( idx );
1337  QString labelText = fieldName;
1338  labelText.replace( '&', QStringLiteral( "&&" ) ); // need to escape '&' or they'll be replace by _ in the label text
1339 
1340  const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, field.name() );
1341 
1342  if ( widgetSetup.type() == QLatin1String( "Hidden" ) )
1343  continue;
1344 
1345  bool labelOnTop = mLayer->editFormConfig().labelOnTop( idx );
1346 
1347  // This will also create the widget
1348  QLabel *l = new QLabel( labelText );
1349  l->setToolTip( QStringLiteral( "<b>%1</b><p>%2</p>" ).arg( fieldName, field.comment() ) );
1350  QSvgWidget *i = new QSvgWidget();
1351  i->setFixedSize( 18, 18 );
1352 
1353  QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, idx, widgetSetup.config(), nullptr, this, mContext );
1354 
1355  QWidget *w = nullptr;
1356  if ( eww )
1357  {
1358  QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
1359  w = formWidget;
1360  mFormEditorWidgets.insert( idx, formWidget );
1361  mFormWidgets.append( formWidget );
1362  formWidget->createSearchWidgetWrappers( mContext );
1363 
1364  l->setBuddy( eww->widget() );
1365  }
1366  else
1367  {
1368  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() ) ) );
1369  }
1370 
1371 
1372  if ( w )
1373  w->setObjectName( field.name() );
1374 
1375  if ( eww )
1376  {
1377  addWidgetWrapper( eww );
1378  mIconMap[eww->widget()] = i;
1379  }
1380 
1381  if ( labelOnTop )
1382  {
1383  gridLayout->addWidget( l, row++, 0, 1, 2 );
1384  gridLayout->addWidget( w, row++, 0, 1, 2 );
1385  gridLayout->addWidget( i, row++, 0, 1, 2 );
1386  }
1387  else
1388  {
1389  gridLayout->addWidget( l, row, 0 );
1390  gridLayout->addWidget( w, row, 1 );
1391  gridLayout->addWidget( i, row++, 2 );
1392  }
1393  }
1394 
1395  const QList<QgsRelation> relations = QgsProject::instance()->relationManager()->referencedRelations( mLayer );
1396  for ( const QgsRelation &rel : relations )
1397  {
1398  QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( rel, mContext );
1399 
1401  formWidget->createSearchWidgetWrappers( mContext );
1402  gridLayout->addWidget( formWidget, row++, 0, 1, 2 );
1403 
1404  mWidgets.append( rww );
1405  mFormWidgets.append( formWidget );
1406  }
1407 
1408  if ( QgsProject::instance()->relationManager()->referencedRelations( mLayer ).isEmpty() )
1409  {
1410  QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1411  gridLayout->addItem( spacerItem, row, 0 );
1412  gridLayout->setRowStretch( row, 1 );
1413  row++;
1414  }
1415  }
1416 
1417  if ( !mButtonBox )
1418  {
1419  mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1420  mButtonBox->setObjectName( QStringLiteral( "buttonBox" ) );
1421  layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
1422  }
1423  mButtonBox->setVisible( buttonBoxVisible );
1424 
1425  if ( !mSearchButtonBox )
1426  {
1427  mSearchButtonBox = new QWidget();
1428  QHBoxLayout *boxLayout = new QHBoxLayout();
1429  boxLayout->setMargin( 0 );
1430  boxLayout->setContentsMargins( 0, 0, 0, 0 );
1431  mSearchButtonBox->setLayout( boxLayout );
1432  mSearchButtonBox->setObjectName( QStringLiteral( "searchButtonBox" ) );
1433 
1434  QPushButton *clearButton = new QPushButton( tr( "&Reset Form" ), mSearchButtonBox );
1435  connect( clearButton, &QPushButton::clicked, this, &QgsAttributeForm::resetSearch );
1436  boxLayout->addWidget( clearButton );
1437  boxLayout->addStretch( 1 );
1438 
1439  QPushButton *flashButton = new QPushButton();
1440  flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1441  flashButton->setText( tr( "&Flash Features" ) );
1442  connect( flashButton, &QToolButton::clicked, this, &QgsAttributeForm::searchFlash );
1443  boxLayout->addWidget( flashButton );
1444 
1445  QPushButton *zoomButton = new QPushButton();
1446  zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1447  zoomButton->setText( tr( "&Zoom to Features" ) );
1448  connect( zoomButton, &QToolButton::clicked, this, &QgsAttributeForm::searchZoomTo );
1449  boxLayout->addWidget( zoomButton );
1450 
1451  QToolButton *selectButton = new QToolButton();
1452  selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1453  selectButton->setText( tr( "&Select Features" ) );
1454  selectButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
1455  selectButton->setPopupMode( QToolButton::MenuButtonPopup );
1456  selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
1457  connect( selectButton, &QToolButton::clicked, this, &QgsAttributeForm::searchSetSelection );
1458  QMenu *selectMenu = new QMenu( selectButton );
1459  QAction *selectAction = new QAction( tr( "Select Features" ), selectMenu );
1460  selectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
1461  connect( selectAction, &QAction::triggered, this, &QgsAttributeForm::searchSetSelection );
1462  selectMenu->addAction( selectAction );
1463  QAction *addSelectAction = new QAction( tr( "Add to Current Selection" ), selectMenu );
1464  addSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectAdd.svg" ) ) );
1465  connect( addSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchAddToSelection );
1466  selectMenu->addAction( addSelectAction );
1467  QAction *deselectAction = new QAction( tr( "Remove from Current Selection" ), selectMenu );
1468  deselectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectRemove.svg" ) ) );
1469  connect( deselectAction, &QAction::triggered, this, &QgsAttributeForm::searchRemoveFromSelection );
1470  selectMenu->addAction( deselectAction );
1471  QAction *filterSelectAction = new QAction( tr( "Filter Current Selection" ), selectMenu );
1472  filterSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectIntersect.svg" ) ) );
1473  connect( filterSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchIntersectSelection );
1474  selectMenu->addAction( filterSelectAction );
1475  selectButton->setMenu( selectMenu );
1476  boxLayout->addWidget( selectButton );
1477 
1478  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
1479  {
1480  QToolButton *filterButton = new QToolButton();
1481  filterButton->setText( tr( "Filter Features" ) );
1482  filterButton->setPopupMode( QToolButton::MenuButtonPopup );
1483  filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1484  connect( filterButton, &QToolButton::clicked, this, &QgsAttributeForm::filterTriggered );
1485  QMenu *filterMenu = new QMenu( filterButton );
1486  QAction *filterAndAction = new QAction( tr( "Filter Within (\"AND\")" ), filterMenu );
1487  connect( filterAndAction, &QAction::triggered, this, &QgsAttributeForm::filterAndTriggered );
1488  filterMenu->addAction( filterAndAction );
1489  QAction *filterOrAction = new QAction( tr( "Extend Filter (\"OR\")" ), filterMenu );
1490  connect( filterOrAction, &QAction::triggered, this, &QgsAttributeForm::filterOrTriggered );
1491  filterMenu->addAction( filterOrAction );
1492  filterButton->setMenu( filterMenu );
1493  boxLayout->addWidget( filterButton );
1494  }
1495  else
1496  {
1497  QPushButton *closeButton = new QPushButton( tr( "Close" ), mSearchButtonBox );
1498  connect( closeButton, &QPushButton::clicked, this, &QgsAttributeForm::closed );
1499  closeButton->setShortcut( Qt::Key_Escape );
1500  boxLayout->addWidget( closeButton );
1501  }
1502 
1503  layout->addWidget( mSearchButtonBox );
1504  }
1505  mSearchButtonBox->setVisible( mMode == QgsAttributeEditorContext::SearchMode );
1506 
1507  afterWidgetInit();
1508 
1509  connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
1510  connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
1511 
1512  connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsAttributeForm::synchronizeEnabledState );
1513  connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsAttributeForm::synchronizeEnabledState );
1514 
1515  // This triggers a refresh of the form widget and gives a chance to re-format the
1516  // value to those widgets that have a different representation when in edit mode
1519 
1520 
1521  const auto constMInterfaces = mInterfaces;
1522  for ( QgsAttributeFormInterface *iface : constMInterfaces )
1523  {
1524  iface->initForm();
1525  }
1526 
1528  {
1529  hideButtonBox();
1530  }
1531 
1532  QApplication::restoreOverrideCursor();
1533 }
1534 
1535 void QgsAttributeForm::cleanPython()
1536 {
1537  if ( !mPyFormVarName.isNull() )
1538  {
1539  QString expr = QStringLiteral( "if '%1' in locals(): del %1\n" ).arg( mPyFormVarName );
1540  QgsPythonRunner::run( expr );
1541  }
1542 }
1543 
1544 void QgsAttributeForm::initPython()
1545 {
1546  cleanPython();
1547 
1548  // Init Python, if init function is not empty and the combo indicates
1549  // the source for the function code
1550  if ( !mLayer->editFormConfig().initFunction().isEmpty()
1552  {
1553 
1554  QString initFunction = mLayer->editFormConfig().initFunction();
1555  QString initFilePath = mLayer->editFormConfig().initFilePath();
1556  QString initCode;
1557 
1558  switch ( mLayer->editFormConfig().initCodeSource() )
1559  {
1561  if ( ! initFilePath.isEmpty() )
1562  {
1563  QFile inputFile( initFilePath );
1564 
1565  if ( inputFile.open( QFile::ReadOnly ) )
1566  {
1567  // Read it into a string
1568  QTextStream inf( &inputFile );
1569  initCode = inf.readAll();
1570  inputFile.close();
1571  }
1572  else // The file couldn't be opened
1573  {
1574  QgsLogger::warning( QStringLiteral( "The external python file path %1 could not be opened!" ).arg( initFilePath ) );
1575  }
1576  }
1577  else
1578  {
1579  QgsLogger::warning( QStringLiteral( "The external python file path is empty!" ) );
1580  }
1581  break;
1582 
1584  initCode = mLayer->editFormConfig().initCode();
1585  if ( initCode.isEmpty() )
1586  {
1587  QgsLogger::warning( QStringLiteral( "The python code provided in the dialog is empty!" ) );
1588  }
1589  break;
1590 
1593  default:
1594  // Nothing to do: the function code should be already in the environment
1595  break;
1596  }
1597 
1598  // If we have a function code, run it
1599  if ( ! initCode.isEmpty() )
1600  {
1601  QgsPythonRunner::run( initCode );
1602  }
1603 
1604  QgsPythonRunner::run( QStringLiteral( "import inspect" ) );
1605  QString numArgs;
1606 
1607  // Check for eval result
1608  if ( QgsPythonRunner::eval( QStringLiteral( "len(inspect.getargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
1609  {
1610  static int sFormId = 0;
1611  mPyFormVarName = QStringLiteral( "_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
1612 
1613  QString form = QStringLiteral( "%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
1614  .arg( mPyFormVarName )
1615  .arg( ( quint64 ) this );
1616 
1617  QgsPythonRunner::run( form );
1618 
1619  QgsDebugMsg( QStringLiteral( "running featureForm init: %1" ).arg( mPyFormVarName ) );
1620 
1621  // Legacy
1622  if ( numArgs == QLatin1String( "3" ) )
1623  {
1624  addInterface( new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName, this ) );
1625  }
1626  else
1627  {
1628  // If we get here, it means that the function doesn't accept three arguments
1629  QMessageBox msgBox;
1630  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 ) );
1631  msgBox.exec();
1632 #if 0
1633  QString expr = QString( "%1(%2)" )
1634  .arg( mLayer->editFormInit() )
1635  .arg( mPyFormVarName );
1636  QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr, "QgsAttributeFormInterface" );
1637  if ( iface )
1638  addInterface( iface );
1639 #endif
1640  }
1641  }
1642  else
1643  {
1644  // If we get here, it means that inspect couldn't find the function
1645  QMessageBox msgBox;
1646  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 ) );
1647  msgBox.exec();
1648  }
1649  }
1650 }
1651 
1652 QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement *widgetDef, QWidget *parent, QgsVectorLayer *vl, QgsAttributeEditorContext &context )
1653 {
1654  WidgetInfo newWidgetInfo;
1655 
1656  switch ( widgetDef->type() )
1657  {
1659  {
1660  const QgsAttributeEditorField *fieldDef = dynamic_cast<const QgsAttributeEditorField *>( widgetDef );
1661  if ( !fieldDef )
1662  break;
1663 
1664  const QgsFields fields = vl->fields();
1665  int fldIdx = fields.lookupField( fieldDef->name() );
1666  if ( fldIdx < fields.count() && fldIdx >= 0 )
1667  {
1668  const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fieldDef->name() );
1669 
1670  QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, fldIdx, widgetSetup.config(), nullptr, this, mContext );
1671  QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
1672  mFormEditorWidgets.insert( fldIdx, formWidget );
1673  mFormWidgets.append( formWidget );
1674 
1675  formWidget->createSearchWidgetWrappers( mContext );
1676 
1677  newWidgetInfo.widget = formWidget;
1678  addWidgetWrapper( eww );
1679 
1680  newWidgetInfo.widget->setObjectName( fields.at( fldIdx ).name() );
1681  newWidgetInfo.hint = fields.at( fldIdx ).comment();
1682  }
1683 
1684  newWidgetInfo.labelOnTop = mLayer->editFormConfig().labelOnTop( fldIdx );
1685  newWidgetInfo.labelText = mLayer->attributeDisplayName( fldIdx );
1686  newWidgetInfo.labelText.replace( '&', QStringLiteral( "&&" ) ); // need to escape '&' or they'll be replace by _ in the label text
1687  newWidgetInfo.toolTip = QStringLiteral( "<b>%1</b><p>%2</p>" ).arg( mLayer->attributeDisplayName( fldIdx ), newWidgetInfo.hint );
1688  newWidgetInfo.showLabel = widgetDef->showLabel();
1689 
1690  break;
1691  }
1692 
1694  {
1695  const QgsAttributeEditorRelation *relDef = static_cast<const QgsAttributeEditorRelation *>( widgetDef );
1696 
1697  QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( relDef->relation(), context );
1698 
1699  rww->setShowLabel( relDef->showLabel() );
1700  rww->setShowLinkButton( relDef->showLinkButton() );
1701  rww->setShowUnlinkButton( relDef->showUnlinkButton() );
1702 
1704  formWidget->createSearchWidgetWrappers( mContext );
1705 
1706  mWidgets.append( rww );
1707  mFormWidgets.append( formWidget );
1708 
1709  newWidgetInfo.widget = formWidget;
1710  newWidgetInfo.labelText = QString();
1711  newWidgetInfo.labelOnTop = true;
1712  break;
1713  }
1714 
1716  {
1717  const QgsAttributeEditorContainer *container = dynamic_cast<const QgsAttributeEditorContainer *>( widgetDef );
1718  if ( !container )
1719  break;
1720 
1721  int columnCount = container->columnCount();
1722 
1723  if ( columnCount <= 0 )
1724  columnCount = 1;
1725 
1726  QString widgetName;
1727  QWidget *myContainer = nullptr;
1728  if ( container->isGroupBox() )
1729  {
1730  QGroupBox *groupBox = new QGroupBox( parent );
1731  widgetName = QStringLiteral( "QGroupBox" );
1732  if ( container->showLabel() )
1733  groupBox->setTitle( container->name() );
1734  myContainer = groupBox;
1735  newWidgetInfo.widget = myContainer;
1736  }
1737  else
1738  {
1739  myContainer = new QWidget();
1740 
1741  if ( context.formMode() != QgsAttributeEditorContext::Embed )
1742  {
1743  QgsScrollArea *scrollArea = new QgsScrollArea( parent );
1744 
1745  scrollArea->setWidget( myContainer );
1746  scrollArea->setWidgetResizable( true );
1747  scrollArea->setFrameShape( QFrame::NoFrame );
1748  widgetName = QStringLiteral( "QScrollArea QWidget" );
1749 
1750  newWidgetInfo.widget = scrollArea;
1751  }
1752  else
1753  {
1754  newWidgetInfo.widget = myContainer;
1755  widgetName = QStringLiteral( "QWidget" );
1756  }
1757  }
1758 
1759  if ( container->backgroundColor().isValid() )
1760  {
1761  QString style {QStringLiteral( "background-color: %1;" ).arg( container->backgroundColor().name() )};
1762  newWidgetInfo.widget->setStyleSheet( style );
1763  }
1764 
1765  QGridLayout *gbLayout = new QGridLayout();
1766  myContainer->setLayout( gbLayout );
1767 
1768  int row = 0;
1769  int column = 0;
1770 
1771  const QList<QgsAttributeEditorElement *> children = container->children();
1772 
1773  for ( QgsAttributeEditorElement *childDef : children )
1774  {
1775  WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
1776 
1777  if ( childDef->type() == QgsAttributeEditorElement::AeTypeContainer )
1778  {
1779  QgsAttributeEditorContainer *containerDef = static_cast<QgsAttributeEditorContainer *>( childDef );
1780  if ( containerDef->visibilityExpression().enabled() )
1781  {
1782  registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().data() ) );
1783  }
1784  }
1785 
1786  if ( widgetInfo.labelText.isNull() )
1787  {
1788  gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1789  column += 2;
1790  }
1791  else
1792  {
1793  QLabel *mypLabel = new QLabel( widgetInfo.labelText );
1794  mypLabel->setToolTip( widgetInfo.toolTip );
1795  if ( columnCount > 1 && !widgetInfo.labelOnTop )
1796  {
1797  mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1798  }
1799 
1800  mypLabel->setBuddy( widgetInfo.widget );
1801 
1802  if ( widgetInfo.labelOnTop )
1803  {
1804  QVBoxLayout *c = new QVBoxLayout();
1805  mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1806  c->layout()->addWidget( mypLabel );
1807  c->layout()->addWidget( widgetInfo.widget );
1808  gbLayout->addLayout( c, row, column, 1, 2 );
1809  column += 2;
1810  }
1811  else
1812  {
1813  gbLayout->addWidget( mypLabel, row, column++ );
1814  gbLayout->addWidget( widgetInfo.widget, row, column++ );
1815  }
1816  }
1817 
1818  if ( column >= columnCount * 2 )
1819  {
1820  column = 0;
1821  row += 1;
1822  }
1823  }
1824  QWidget *spacer = new QWidget();
1825  spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
1826  gbLayout->addWidget( spacer, ++row, 0 );
1827  gbLayout->setRowStretch( row, 1 );
1828 
1829  newWidgetInfo.labelText = QString();
1830  newWidgetInfo.labelOnTop = true;
1831  break;
1832  }
1833 
1835  {
1836  const QgsAttributeEditorQmlElement *elementDef = static_cast<const QgsAttributeEditorQmlElement *>( widgetDef );
1837 
1838  QgsQmlWidgetWrapper *qmlWrapper = new QgsQmlWidgetWrapper( mLayer, nullptr, this );
1839  qmlWrapper->setQmlCode( elementDef->qmlCode() );
1840  qmlWrapper->setConfig( mLayer->editFormConfig().widgetConfig( elementDef->name() ) );
1841  context.setAttributeFormMode( mMode );
1842  qmlWrapper->setContext( context );
1843 
1844  mWidgets.append( qmlWrapper );
1845 
1846  newWidgetInfo.widget = qmlWrapper->widget();
1847  newWidgetInfo.labelText = elementDef->name();
1848  newWidgetInfo.labelOnTop = true;
1849  newWidgetInfo.showLabel = widgetDef->showLabel();
1850  break;
1851  }
1852 
1854  {
1855  const QgsAttributeEditorHtmlElement *elementDef = static_cast<const QgsAttributeEditorHtmlElement *>( widgetDef );
1856 
1857  QgsHtmlWidgetWrapper *htmlWrapper = new QgsHtmlWidgetWrapper( mLayer, nullptr, this );
1858  context.setAttributeFormMode( mMode );
1859  htmlWrapper->setHtmlCode( elementDef->htmlCode() );
1860  htmlWrapper->reinitWidget();
1861  htmlWrapper->setConfig( mLayer->editFormConfig().widgetConfig( elementDef->name() ) );
1862 
1863  mWidgets.append( htmlWrapper );
1864 
1865  newWidgetInfo.widget = htmlWrapper->widget();
1866  newWidgetInfo.labelText = elementDef->name();
1867  newWidgetInfo.labelOnTop = true;
1868  newWidgetInfo.showLabel = widgetDef->showLabel();
1869  break;
1870  }
1871 
1872  default:
1873  QgsDebugMsg( QStringLiteral( "Unknown attribute editor widget type encountered..." ) );
1874  break;
1875  }
1876 
1877  newWidgetInfo.showLabel = widgetDef->showLabel();
1878 
1879  return newWidgetInfo;
1880 }
1881 
1882 void QgsAttributeForm::addWidgetWrapper( QgsEditorWidgetWrapper *eww )
1883 {
1884  const auto constMWidgets = mWidgets;
1885  for ( QgsWidgetWrapper *ww : constMWidgets )
1886  {
1887  QgsEditorWidgetWrapper *meww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1888  if ( meww )
1889  {
1890  if ( meww->field() == eww->field() )
1891  {
1892  connect( meww, static_cast<void ( QgsEditorWidgetWrapper::* )( const QVariant & )>( &QgsEditorWidgetWrapper::valueChanged ), eww, &QgsEditorWidgetWrapper::setValue );
1893  connect( eww, static_cast<void ( QgsEditorWidgetWrapper::* )( const QVariant & )>( &QgsEditorWidgetWrapper::valueChanged ), meww, &QgsEditorWidgetWrapper::setValue );
1894  break;
1895  }
1896  }
1897  }
1898 
1899  mWidgets.append( eww );
1900 }
1901 
1902 void QgsAttributeForm::createWrappers()
1903 {
1904  QList<QWidget *> myWidgets = findChildren<QWidget *>();
1905  const QList<QgsField> fields = mLayer->fields().toList();
1906 
1907  const auto constMyWidgets = myWidgets;
1908  for ( QWidget *myWidget : constMyWidgets )
1909  {
1910  // Check the widget's properties for a relation definition
1911  QVariant vRel = myWidget->property( "qgisRelation" );
1912  if ( vRel.isValid() )
1913  {
1915  QgsRelation relation = relMgr->relation( vRel.toString() );
1916  if ( relation.isValid() )
1917  {
1918  QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
1919  rww->setConfig( mLayer->editFormConfig().widgetConfig( relation.id() ) );
1920  rww->setContext( mContext );
1921  rww->widget(); // Will initialize the widget
1922  mWidgets.append( rww );
1923  }
1924  }
1925  else
1926  {
1927  const auto constFields = fields;
1928  for ( const QgsField &field : constFields )
1929  {
1930  if ( field.name() == myWidget->objectName() )
1931  {
1932  int idx = mLayer->fields().lookupField( field.name() );
1933 
1934  QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( mLayer, idx, myWidget, this, mContext );
1935  addWidgetWrapper( eww );
1936  }
1937  }
1938  }
1939  }
1940 }
1941 
1942 void QgsAttributeForm::afterWidgetInit()
1943 {
1944  bool isFirstEww = true;
1945 
1946  const auto constMWidgets = mWidgets;
1947  for ( QgsWidgetWrapper *ww : constMWidgets )
1948  {
1949  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1950 
1951  if ( eww )
1952  {
1953  if ( isFirstEww )
1954  {
1955  setFocusProxy( eww->widget() );
1956  isFirstEww = false;
1957  }
1958 
1959  connect( eww, static_cast<void ( QgsEditorWidgetWrapper::* )( const QVariant & )>( &QgsEditorWidgetWrapper::valueChanged ), this, &QgsAttributeForm::onAttributeChanged );
1960  connect( eww, &QgsEditorWidgetWrapper::constraintStatusChanged, this, &QgsAttributeForm::onConstraintStatusChanged );
1961  }
1962  }
1963 }
1964 
1965 
1966 bool QgsAttributeForm::eventFilter( QObject *object, QEvent *e )
1967 {
1968  Q_UNUSED( object )
1969 
1970  if ( e->type() == QEvent::KeyPress )
1971  {
1972  QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( e );
1973  if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
1974  {
1975  // Re-emit to this form so it will be forwarded to parent
1976  event( e );
1977  return true;
1978  }
1979  }
1980 
1981  return false;
1982 }
1983 
1984 void QgsAttributeForm::scanForEqualAttributes( QgsFeatureIterator &fit, QSet< int > &mixedValueFields, QHash< int, QVariant > &fieldSharedValues ) const
1985 {
1986  mixedValueFields.clear();
1987  fieldSharedValues.clear();
1988 
1989  QgsFeature f;
1990  bool first = true;
1991  while ( fit.nextFeature( f ) )
1992  {
1993  for ( int i = 0; i < mLayer->fields().count(); ++i )
1994  {
1995  if ( mixedValueFields.contains( i ) )
1996  continue;
1997 
1998  if ( first )
1999  {
2000  fieldSharedValues[i] = f.attribute( i );
2001  }
2002  else
2003  {
2004  if ( fieldSharedValues.value( i ) != f.attribute( i ) )
2005  {
2006  fieldSharedValues.remove( i );
2007  mixedValueFields.insert( i );
2008  }
2009  }
2010  }
2011  first = false;
2012 
2013  if ( mixedValueFields.count() == mLayer->fields().count() )
2014  {
2015  // all attributes are mixed, no need to keep scanning
2016  break;
2017  }
2018  }
2019 }
2020 
2021 
2022 void QgsAttributeForm::layerSelectionChanged()
2023 {
2024  switch ( mMode )
2025  {
2031  break;
2032 
2034  resetMultiEdit( true );
2035  break;
2036  }
2037 }
2038 
2040 {
2041  mIsSettingMultiEditFeatures = true;
2042  mMultiEditFeatureIds = fids;
2043 
2044  if ( fids.isEmpty() )
2045  {
2046  // no selected features
2047  QMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
2048  for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
2049  {
2050  wIt.value()->initialize( QVariant() );
2051  }
2052  mIsSettingMultiEditFeatures = false;
2053  return;
2054  }
2055 
2056  QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFids( fids ) );
2057 
2058  // Scan through all features to determine which attributes are initially the same
2059  QSet< int > mixedValueFields;
2060  QHash< int, QVariant > fieldSharedValues;
2061  scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
2062 
2063  // also fetch just first feature
2064  fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFid( *fids.constBegin() ) );
2065  QgsFeature firstFeature;
2066  fit.nextFeature( firstFeature );
2067 
2068  const auto constMixedValueFields = mixedValueFields;
2069  for ( int field : constMixedValueFields )
2070  {
2071  if ( QgsAttributeFormEditorWidget *w = mFormEditorWidgets.value( field, nullptr ) )
2072  {
2073  w->initialize( firstFeature.attribute( field ), true );
2074  }
2075  }
2076  QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
2077  for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
2078  {
2079  if ( QgsAttributeFormEditorWidget *w = mFormEditorWidgets.value( sharedValueIt.key(), nullptr ) )
2080  {
2081  w->initialize( sharedValueIt.value(), false );
2082  }
2083  }
2084  mIsSettingMultiEditFeatures = false;
2085 }
2086 
2088 {
2089  if ( mOwnsMessageBar )
2090  delete mMessageBar;
2091  mOwnsMessageBar = false;
2092  mMessageBar = messageBar;
2093 }
2094 
2096 {
2098  {
2099  Q_ASSERT( false );
2100  }
2101 
2102  QStringList filters;
2103  for ( QgsAttributeFormWidget *widget : mFormWidgets )
2104  {
2105  QString filter = widget->currentFilterExpression();
2106  if ( !filter.isNull() )
2107  filters << '(' + filter + ')';
2108  }
2109 
2110  return filters.join( QStringLiteral( " AND " ) );
2111 }
2112 
2113 int QgsAttributeForm::messageTimeout()
2114 {
2115  QgsSettings settings;
2116  return settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt();
2117 }
2118 
2119 void QgsAttributeForm::ContainerInformation::apply( QgsExpressionContext *expressionContext )
2120 {
2121  bool newVisibility = expression.evaluate( expressionContext ).toBool();
2122 
2123  if ( newVisibility != isVisible )
2124  {
2125  if ( tabWidget )
2126  {
2127  tabWidget->setTabVisible( widget, newVisibility );
2128  }
2129  else
2130  {
2131  widget->setVisible( newVisibility );
2132  }
2133 
2134  isVisible = newVisibility;
2135  }
2136 }
2137 
2138 void QgsAttributeForm::updateJoinedFields( const QgsEditorWidgetWrapper &eww )
2139 {
2140  if ( !eww.layer()->fields().exists( eww.fieldIdx() ) )
2141  return;
2142 
2143  QgsFeature formFeature;
2144  QgsField field = eww.layer()->fields().field( eww.fieldIdx() );
2145  QList<const QgsVectorLayerJoinInfo *> infos = eww.layer()->joinBuffer()->joinsWhereFieldIsId( field );
2146 
2147  if ( infos.count() == 0 || !currentFormFeature( formFeature ) )
2148  return;
2149 
2150  const QString hint = tr( "No feature joined" );
2151  const auto constInfos = infos;
2152  for ( const QgsVectorLayerJoinInfo *info : constInfos )
2153  {
2154  if ( !info->isDynamicFormEnabled() )
2155  continue;
2156 
2157  QgsFeature joinFeature = mLayer->joinBuffer()->joinedFeatureOf( info, formFeature );
2158 
2159  mJoinedFeatures[info] = joinFeature;
2160 
2161  if ( info->hasSubset() )
2162  {
2163  const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( *info );
2164 
2165  const auto constSubsetNames = subsetNames;
2166  for ( const QString &field : constSubsetNames )
2167  {
2168  QString prefixedName = info->prefixedFieldName( field );
2169  QVariant val;
2170  QString hintText = hint;
2171 
2172  if ( joinFeature.isValid() )
2173  {
2174  val = joinFeature.attribute( field );
2175  hintText.clear();
2176  }
2177 
2178  changeAttribute( prefixedName, val, hintText );
2179  }
2180  }
2181  else
2182  {
2183  const QgsFields joinFields = joinFeature.fields();
2184  for ( const QgsField &field : joinFields )
2185  {
2186  QString prefixedName = info->prefixedFieldName( field );
2187  QVariant val;
2188  QString hintText = hint;
2189 
2190  if ( joinFeature.isValid() )
2191  {
2192  val = joinFeature.attribute( field.name() );
2193  hintText.clear();
2194  }
2195 
2196  changeAttribute( prefixedName, val, hintText );
2197  }
2198  }
2199  }
2200 }
2201 
2202 bool QgsAttributeForm::fieldIsEditable( int fieldIndex ) const
2203 {
2204  bool editable = false;
2205 
2206  if ( mLayer->fields().fieldOrigin( fieldIndex ) == QgsFields::OriginJoin )
2207  {
2208  int srcFieldIndex;
2209  const QgsVectorLayerJoinInfo *info = mLayer->joinBuffer()->joinForFieldIndex( fieldIndex, mLayer->fields(), srcFieldIndex );
2210 
2211  if ( info && !info->hasUpsertOnEdit() && mMode == QgsAttributeEditorContext::AddFeatureMode )
2212  editable = false;
2213  else if ( info && info->isEditable() && info->joinLayer()->isEditable() )
2214  editable = fieldIsEditable( *( info->joinLayer() ), srcFieldIndex, mFeature.id() );
2215  }
2216  else
2217  editable = fieldIsEditable( *mLayer, fieldIndex, mFeature.id() );
2218 
2219  return editable;
2220 }
2221 
2222 bool QgsAttributeForm::fieldIsEditable( const QgsVectorLayer &layer, int fieldIndex, QgsFeatureId fid ) const
2223 {
2224  return !layer.editFormConfig().readOnly( fieldIndex ) &&
2226 }
2227 
2228 void QgsAttributeForm::updateIcon( QgsEditorWidgetWrapper *eww )
2229 {
2230  if ( !eww->widget() || !mIconMap[eww->widget()] )
2231  return;
2232 
2233  // no icon by default
2234  mIconMap[eww->widget()]->hide();
2235 
2236  if ( !eww->widget()->isEnabled() && mLayer->isEditable() )
2237  {
2238  if ( mLayer->fields().fieldOrigin( eww->fieldIdx() ) == QgsFields::OriginJoin )
2239  {
2240  int srcFieldIndex;
2241  const QgsVectorLayerJoinInfo *info = mLayer->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), mLayer->fields(), srcFieldIndex );
2242 
2243  if ( !info )
2244  return;
2245 
2246  if ( !info->isEditable() )
2247  {
2248  const QString file = QStringLiteral( "/mIconJoinNotEditable.svg" );
2249  const QString tooltip = tr( "Join settings do not allow editing" );
2250  reloadIcon( file, tooltip, mIconMap[eww->widget()] );
2251  }
2252  else if ( mMode == QgsAttributeEditorContext::AddFeatureMode && !info->hasUpsertOnEdit() )
2253  {
2254  const QString file = QStringLiteral( "mIconJoinHasNotUpsertOnEdit.svg" );
2255  const QString tooltip = tr( "Join settings do not allow upsert on edit" );
2256  reloadIcon( file, tooltip, mIconMap[eww->widget()] );
2257  }
2258  else if ( !info->joinLayer()->isEditable() )
2259  {
2260  const QString file = QStringLiteral( "/mIconJoinedLayerNotEditable.svg" );
2261  const QString tooltip = tr( "Joined layer is not toggled editable" );
2262  reloadIcon( file, tooltip, mIconMap[eww->widget()] );
2263  }
2264  }
2265  }
2266 }
2267 
2268 void QgsAttributeForm::reloadIcon( const QString &file, const QString &tooltip, QSvgWidget *sw )
2269 {
2270  sw->load( QgsApplication::iconPath( file ) );
2271  sw->setToolTip( tooltip );
2272  sw->show();
2273 }
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
virtual QgsVectorDataProvider::Capabilities capabilities() const
Returns flags containing the supported capabilities.
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:624
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.
void setQmlCode(const QString &qmlCode)
writes the qmlCode into a temporary file
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) ...
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:59
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.
void refreshFeature()
reload current feature
virtual void setValue(const QVariant &value)=0
Is called, when the value of the widget needs to be changed.
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
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.
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:625
#define FID_IS_NEW(fid)
Definition: qgsfeatureid.h:28
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.
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.
void valueChanged(const QVariant &value)
Emit this signal, whenever the value changed.
Single edit mode, for editing a single feature.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:438
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:82
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 readOnly(int idx) const
This returns true if the field is manually set to read only or if the field does not support editing ...
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer&#39;s data provider, it may be nullptr.
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, NULL values are treated as equal...
Definition: qgis.cpp:289
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
QString attributeDisplayName(int index) const
Convenience function that returns the attribute alias if defined or the field name else...
Allows modification of attribute values.
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
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.
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.