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