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