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