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