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