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