QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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 "qgsattributeeditorcontainer.h"
22 #include "qgsattributeeditorfield.h"
23 #include "qgsattributeeditorrelation.h"
24 #include "qgsattributeeditorqmlelement.h"
25 #include "qgsattributeeditorhtmlelement.h"
27 #include "qgsfeatureiterator.h"
28 #include "qgsgui.h"
29 #include "qgsproject.h"
30 #include "qgspythonrunner.h"
32 #include "qgsvectordataprovider.h"
34 #include "qgsmessagebar.h"
35 #include "qgsmessagebaritem.h"
37 #include "qgseditorwidgetwrapper.h"
38 #include "qgsrelationmanager.h"
39 #include "qgslogger.h"
40 #include "qgstabwidget.h"
41 #include "qgssettings.h"
42 #include "qgsscrollarea.h"
44 #include "qgsvectorlayerutils.h"
45 #include "qgsqmlwidgetwrapper.h"
46 #include "qgshtmlwidgetwrapper.h"
47 #include "qgsapplication.h"
49 #include "qgsfeaturerequest.h"
50 #include "qgstexteditwrapper.h"
51 #include "qgsfieldmodel.h"
52 
53 #include <QDir>
54 #include <QTextStream>
55 #include <QFileInfo>
56 #include <QFile>
57 #include <QFormLayout>
58 #include <QGridLayout>
59 #include <QGroupBox>
60 #include <QKeyEvent>
61 #include <QLabel>
62 #include <QPushButton>
63 #include <QUiLoader>
64 #include <QMessageBox>
65 #include <QToolButton>
66 #include <QMenu>
67 
68 int QgsAttributeForm::sFormCounter = 0;
69 
70 QgsAttributeForm::QgsAttributeForm( QgsVectorLayer *vl, const QgsFeature &feature, const QgsAttributeEditorContext &context, QWidget *parent )
71  : QWidget( parent )
72  , mLayer( vl )
73  , mOwnsMessageBar( true )
74  , mContext( context )
75  , mFormNr( sFormCounter++ )
76  , mIsSaving( false )
77  , mPreventFeatureRefresh( false )
78  , mIsSettingMultiEditFeatures( false )
79  , mUnsavedMultiEditChanges( false )
80  , mEditCommandMessage( tr( "Attributes changed" ) )
81  , mMode( QgsAttributeEditorContext::SingleEditMode )
82 {
83  init();
84  initPython();
86 
87  connect( vl, &QgsVectorLayer::updatedFields, this, &QgsAttributeForm::onUpdatedFields );
88  connect( vl, &QgsVectorLayer::beforeAddingExpressionField, this, &QgsAttributeForm::preventFeatureRefresh );
89  connect( vl, &QgsVectorLayer::beforeRemovingExpressionField, this, &QgsAttributeForm::preventFeatureRefresh );
90  connect( vl, &QgsVectorLayer::selectionChanged, this, &QgsAttributeForm::layerSelectionChanged );
91  connect( this, &QgsAttributeForm::modeChanged, this, &QgsAttributeForm::updateContainersVisibility );
92 
93  updateContainersVisibility();
94  updateLabels();
95 
96 }
97 
99 {
100  cleanPython();
101  qDeleteAll( mInterfaces );
102 }
103 
105 {
106  mButtonBox->hide();
107 
108  // Make sure that changes are taken into account if somebody tries to figure out if there have been some
111 }
112 
114 {
115  mButtonBox->show();
116 
117  disconnect( mLayer, &QgsVectorLayer::beforeModifiedCheck, this, &QgsAttributeForm::save );
118 }
119 
121 {
122  disconnect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
123  disconnect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
124 }
125 
127 {
128  mInterfaces.append( iface );
129 }
130 
132 {
133  return mFeature.isValid() && mLayer->isEditable();
134 }
135 
137 {
138  if ( mode == mMode )
139  return;
140 
142  {
143  //switching out of multi edit mode triggers a save
144  if ( mUnsavedMultiEditChanges )
145  {
146  // prompt for save
147  int res = QMessageBox::question( this, tr( "Multiedit Attributes" ),
148  tr( "Apply changes to edited features?" ), QMessageBox::Yes | QMessageBox::No );
149  if ( res == QMessageBox::Yes )
150  {
151  save();
152  }
153  }
154  clearMultiEditMessages();
155  }
156  mUnsavedMultiEditChanges = false;
157 
158  mMode = mode;
159 
160  if ( mButtonBox->isVisible() && mMode == QgsAttributeEditorContext::SingleEditMode )
161  {
163  }
164  else
165  {
166  disconnect( mLayer, &QgsVectorLayer::beforeModifiedCheck, this, &QgsAttributeForm::save );
167  }
168 
169  //update all form editor widget modes to match
170  for ( QgsAttributeFormWidget *w : qgis::as_const( mFormWidgets ) )
171  {
172  switch ( mode )
173  {
176  break;
177 
180  break;
181 
184  break;
185 
188  break;
189 
192  break;
193 
196  break;
197 
200  break;
201  }
202  }
203  //update all form editor widget modes to match
204  for ( QgsWidgetWrapper *w : qgis::as_const( mWidgets ) )
205  {
206  QgsAttributeEditorContext newContext = w->context();
207  newContext.setAttributeFormMode( mMode );
208  w->setContext( newContext );
209  }
210 
211  bool relationWidgetsVisible = ( mMode != QgsAttributeEditorContext::MultiEditMode && mMode != QgsAttributeEditorContext::AggregateSearchMode );
212  for ( QgsAttributeFormRelationEditorWidget *w : findChildren< QgsAttributeFormRelationEditorWidget * >() )
213  {
214  w->setVisible( relationWidgetsVisible );
215  }
216 
217  switch ( mode )
218  {
220  setFeature( mFeature );
221  mSearchButtonBox->setVisible( false );
222  break;
223 
225  synchronizeEnabledState();
226  mSearchButtonBox->setVisible( false );
227  break;
228 
230  synchronizeEnabledState();
231  mSearchButtonBox->setVisible( false );
232  break;
233 
235  resetMultiEdit( false );
236  synchronizeEnabledState();
237  mSearchButtonBox->setVisible( false );
238  break;
239 
241  mSearchButtonBox->setVisible( true );
242  hideButtonBox();
243  break;
244 
246  mSearchButtonBox->setVisible( false );
247  hideButtonBox();
248  break;
249 
251  setFeature( mFeature );
252  mSearchButtonBox->setVisible( false );
253  break;
254  }
255 
256  emit modeChanged( mMode );
257 }
258 
259 void QgsAttributeForm::changeAttribute( const QString &field, const QVariant &value, const QString &hintText )
260 {
261  const auto constMWidgets = mWidgets;
262  for ( QgsWidgetWrapper *ww : constMWidgets )
263  {
264  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
265  if ( eww )
266  {
267  if ( eww->field().name() == field )
268  {
269  eww->setValues( value, QVariantList() );
270  eww->setHint( hintText );
271  }
272  // see if the field is present in additional fields of the editor widget
273  int index = eww->additionalFields().indexOf( field );
274  if ( index >= 0 )
275  {
276  QVariant mainValue = eww->value();
277  QVariantList additionalFieldValues = eww->additionalFieldValues();
278  additionalFieldValues[index] = value;
279  eww->setValues( mainValue, additionalFieldValues );
280  eww->setHint( hintText );
281  }
282  }
283  }
284 }
285 
287 {
288  mIsSettingFeature = true;
289  mFeature = feature;
290  mCurrentFormFeature = feature;
291 
292  switch ( mMode )
293  {
298  {
299  resetValues();
300 
301  synchronizeEnabledState();
302 
303  // Settings of feature is done when we trigger the attribute form interface
304  // Issue https://github.com/qgis/QGIS/issues/29667
305  mIsSettingFeature = false;
306  const auto constMInterfaces = mInterfaces;
307  for ( QgsAttributeFormInterface *iface : constMInterfaces )
308  {
309  iface->featureChanged();
310  }
311  break;
312  }
315  {
316  resetValues();
317  break;
318  }
320  {
321  //ignore setFeature
322  break;
323  }
324  }
325  mIsSettingFeature = false;
326 }
327 
328 bool QgsAttributeForm::saveEdits( QString *error )
329 {
330  bool success = true;
331  bool changedLayer = false;
332 
333  QgsFeature updatedFeature = QgsFeature( mFeature );
334  if ( mFeature.isValid() || mMode == QgsAttributeEditorContext::AddFeatureMode )
335  {
336  bool doUpdate = false;
337 
338  // An add dialog should perform an action by default
339  // and not only if attributes have "changed"
341  doUpdate = true;
342 
343  QgsAttributes src = mFeature.attributes();
344  QgsAttributes dst = mFeature.attributes();
345 
346  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
347  {
348  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
349  if ( eww )
350  {
351  // check for invalid JSON values
352  QgsTextEditWrapper *textEdit = qobject_cast<QgsTextEditWrapper *>( eww );
353  if ( textEdit && textEdit->isInvalidJSON() )
354  {
355  if ( error )
356  *error = tr( "JSON value for %1 is invalid and has not been saved" ).arg( eww->field().name() );
357  return false;
358  }
359  QVariantList dstVars = QVariantList() << dst.at( eww->fieldIdx() );
360  QVariantList srcVars = QVariantList() << eww->value();
361  QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
362 
363  // append additional fields
364  const QStringList additionalFields = eww->additionalFields();
365  for ( const QString &fieldName : additionalFields )
366  {
367  int idx = eww->layer()->fields().lookupField( fieldName );
368  fieldIndexes << idx;
369  dstVars << dst.at( idx );
370  }
371  srcVars.append( eww->additionalFieldValues() );
372 
373  Q_ASSERT( dstVars.count() == srcVars.count() );
374 
375  for ( int i = 0; i < dstVars.count(); i++ )
376  {
377 
378  if ( !qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() )
379  {
380  dst[fieldIndexes[i]] = srcVars[i];
381 
382  doUpdate = true;
383  }
384  }
385  }
386  }
387 
388  updatedFeature.setAttributes( dst );
389 
390  const auto constMInterfaces = mInterfaces;
391  for ( QgsAttributeFormInterface *iface : constMInterfaces )
392  {
393  if ( !iface->acceptChanges( updatedFeature ) )
394  {
395  doUpdate = false;
396  }
397  }
398 
399  if ( doUpdate )
400  {
402  {
403  mFeature = updatedFeature;
404  }
405  else if ( mMode == QgsAttributeEditorContext::AddFeatureMode )
406  {
407  mFeature.setValid( true );
408  mLayer->beginEditCommand( mEditCommandMessage );
409  bool res = mLayer->addFeature( updatedFeature );
410  if ( res )
411  {
412  mFeature.setAttributes( updatedFeature.attributes() );
413  mLayer->endEditCommand();
415  changedLayer = true;
416  }
417  else
418  mLayer->destroyEditCommand();
419  }
420  else
421  {
422  mLayer->beginEditCommand( mEditCommandMessage );
423 
424  QgsAttributeMap newValues;
425  QgsAttributeMap oldValues;
426 
427  int n = 0;
428  for ( int i = 0; i < dst.count(); ++i )
429  {
430  if ( qgsVariantEqual( dst.at( i ), src.at( i ) ) // If field is not changed...
431  || !dst.at( i ).isValid() // or the widget returns invalid (== do not change)
432  || !fieldIsEditable( i ) ) // or the field cannot be edited ...
433  {
434  continue;
435  }
436 
437  QgsDebugMsgLevel( QStringLiteral( "Updating field %1" ).arg( i ), 2 );
438  QgsDebugMsgLevel( QStringLiteral( "dst:'%1' (type:%2, isNull:%3, isValid:%4)" )
439  .arg( dst.at( i ).toString(), dst.at( i ).typeName() ).arg( dst.at( i ).isNull() ).arg( dst.at( i ).isValid() ), 2 );
440  QgsDebugMsgLevel( QStringLiteral( "src:'%1' (type:%2, isNull:%3, isValid:%4)" )
441  .arg( src.at( i ).toString(), src.at( i ).typeName() ).arg( src.at( i ).isNull() ).arg( src.at( i ).isValid() ), 2 );
442 
443  newValues[i] = dst.at( i );
444  oldValues[i] = src.at( i );
445 
446  n++;
447  }
448 
449  success = mLayer->changeAttributeValues( mFeature.id(), newValues, oldValues );
450 
451  if ( success && n > 0 )
452  {
453  mLayer->endEditCommand();
454  mFeature.setAttributes( dst );
455  changedLayer = true;
456  }
457  else
458  {
459  mLayer->destroyEditCommand();
460  }
461  }
462  }
463  }
464 
465  emit featureSaved( updatedFeature );
466 
467  // [MD] Refresh canvas only when absolutely necessary - it interferes with other stuff (#11361).
468  // This code should be revisited - and the signals should be fired (+ layer repainted)
469  // only when actually doing any changes. I am unsure if it is actually a good idea
470  // to call save() whenever some code asks for vector layer's modified status
471  // (which is the case when attribute table is open)
472  if ( changedLayer )
473  mLayer->triggerRepaint();
474 
475  return success;
476 }
477 
478 bool QgsAttributeForm::updateDefaultValues( const int originIdx )
479 {
480 
481  // Synchronize
482  updateDefaultValueDependencies();
483 
484  if ( !mDefaultValueDependencies.contains( originIdx ) )
485  return false;
486 
487  // create updated Feature
488  QgsFeature updatedFeature = QgsFeature( mFeature );
489  if ( mFeature.isValid() || mMode == QgsAttributeEditorContext::AddFeatureMode )
490  {
491  QgsAttributes dst = mFeature.attributes();
492  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
493  {
494  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
495  if ( eww )
496  {
497  QVariantList dstVars = QVariantList() << dst.at( eww->fieldIdx() );
498  QVariantList srcVars = QVariantList() << eww->value();
499  QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
500 
501  // append additional fields
502  const QStringList additionalFields = eww->additionalFields();
503  for ( const QString &fieldName : additionalFields )
504  {
505  int idx = eww->layer()->fields().lookupField( fieldName );
506  fieldIndexes << idx;
507  dstVars << dst.at( idx );
508  }
509  srcVars.append( eww->additionalFieldValues() );
510 
511  Q_ASSERT( dstVars.count() == srcVars.count() );
512 
513  for ( int i = 0; i < dstVars.count(); i++ )
514  {
515 
516  if ( !qgsVariantEqual( dstVars[i], srcVars[i] ) && srcVars[i].isValid() && fieldIsEditable( fieldIndexes[i] ) )
517  {
518  dst[fieldIndexes[i]] = srcVars[i];
519  }
520  }
521  }
522  }
523  updatedFeature.setAttributes( dst );
524 
525  // go through depending fields and update the fields with defaultexpression
526  QList<QgsWidgetWrapper *> relevantWidgets = mDefaultValueDependencies.values( originIdx );
527  for ( QgsWidgetWrapper *ww : qgis::as_const( relevantWidgets ) )
528  {
529  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
530  if ( eww )
531  {
532  //do not update when when mMode is not AddFeatureMode and it's not applyOnUpdate
534  {
535  continue;
536  }
537 
538  //do not update when this widget is already updating (avoid recursions)
539  if ( mAlreadyUpdatedFields.contains( eww->fieldIdx() ) )
540  continue;
541 
542  QgsExpressionContext context = createExpressionContext( updatedFeature );
543  QString value = mLayer->defaultValue( eww->fieldIdx(), updatedFeature, &context ).toString();
544  eww->setValue( value );
545  }
546  }
547  }
548  return true;
549 }
550 
551 void QgsAttributeForm::resetMultiEdit( bool promptToSave )
552 {
553  if ( promptToSave )
554  save();
555 
556  mUnsavedMultiEditChanges = false;
558 }
559 
560 void QgsAttributeForm::multiEditMessageClicked( const QString &link )
561 {
562  clearMultiEditMessages();
563  resetMultiEdit( link == QLatin1String( "#apply" ) );
564 }
565 
566 void QgsAttributeForm::filterTriggered()
567 {
568  QString filter = createFilterExpression();
569  emit filterExpressionSet( filter, ReplaceFilter );
570  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
572 }
573 
574 void QgsAttributeForm::searchZoomTo()
575 {
576  QString filter = createFilterExpression();
577  if ( filter.isEmpty() )
578  return;
579 
580  emit zoomToFeatures( filter );
581 }
582 
583 void QgsAttributeForm::searchFlash()
584 {
585  QString filter = createFilterExpression();
586  if ( filter.isEmpty() )
587  return;
588 
589  emit flashFeatures( filter );
590 }
591 
592 void QgsAttributeForm::filterAndTriggered()
593 {
594  QString filter = createFilterExpression();
595  if ( filter.isEmpty() )
596  return;
597 
598  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
600  emit filterExpressionSet( filter, FilterAnd );
601 }
602 
603 void QgsAttributeForm::filterOrTriggered()
604 {
605  QString filter = createFilterExpression();
606  if ( filter.isEmpty() )
607  return;
608 
609  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
611  emit filterExpressionSet( filter, FilterOr );
612 }
613 
614 void QgsAttributeForm::pushSelectedFeaturesMessage()
615 {
616  int count = mLayer->selectedFeatureCount();
617  if ( count > 0 )
618  {
619  mMessageBar->pushMessage( QString(),
620  tr( "%n matching feature(s) selected", "matching features", count ),
621  Qgis::Info );
622  }
623  else
624  {
625  mMessageBar->pushMessage( QString(),
626  tr( "No matching features found" ),
627  Qgis::Info );
628  }
629 }
630 
631 void QgsAttributeForm::displayWarning( const QString &message )
632 {
633  mMessageBar->pushMessage( QString(),
634  message,
635  Qgis::Warning );
636 }
637 
638 void QgsAttributeForm::runSearchSelect( QgsVectorLayer::SelectBehavior behavior )
639 {
640  QString filter = createFilterExpression();
641  if ( filter.isEmpty() )
642  return;
643 
644  mLayer->selectByExpression( filter, behavior );
645  pushSelectedFeaturesMessage();
646  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
648 }
649 
650 void QgsAttributeForm::searchSetSelection()
651 {
652  runSearchSelect( QgsVectorLayer::SetSelection );
653 }
654 
655 void QgsAttributeForm::searchAddToSelection()
656 {
657  runSearchSelect( QgsVectorLayer::AddToSelection );
658 }
659 
660 void QgsAttributeForm::searchRemoveFromSelection()
661 {
662  runSearchSelect( QgsVectorLayer::RemoveFromSelection );
663 }
664 
665 void QgsAttributeForm::searchIntersectSelection()
666 {
667  runSearchSelect( QgsVectorLayer::IntersectSelection );
668 }
669 
670 bool QgsAttributeForm::saveMultiEdits()
671 {
672  //find changed attributes
673  QgsAttributeMap newAttributeValues;
674  QMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
675  for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
676  {
677  QgsAttributeFormEditorWidget *w = wIt.value();
678  if ( !w->hasChanged() )
679  continue;
680 
681  if ( !w->currentValue().isValid() // if the widget returns invalid (== do not change)
682  || !fieldIsEditable( wIt.key() ) ) // or the field cannot be edited ...
683  {
684  continue;
685  }
686 
687  // let editor know we've accepted the changes
688  w->changesCommitted();
689 
690  newAttributeValues.insert( wIt.key(), w->currentValue() );
691  }
692 
693  if ( newAttributeValues.isEmpty() )
694  {
695  //nothing to change
696  return true;
697  }
698 
699 #if 0
700  // prompt for save
701  int res = QMessageBox::information( this, tr( "Multiedit Attributes" ),
702  tr( "Edits will be applied to all selected features." ), QMessageBox::Ok | QMessageBox::Cancel );
703  if ( res != QMessageBox::Ok )
704  {
705  resetMultiEdit();
706  return false;
707  }
708 #endif
709 
710  mLayer->beginEditCommand( tr( "Updated multiple feature attributes" ) );
711 
712  bool success = true;
713 
714  const auto constMultiEditFeatureIds = mMultiEditFeatureIds;
715  for ( QgsFeatureId fid : constMultiEditFeatureIds )
716  {
717  QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
718  for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
719  {
720  success &= mLayer->changeAttributeValue( fid, aIt.key(), aIt.value() );
721  }
722  }
723 
724  clearMultiEditMessages();
725  if ( success )
726  {
727  mLayer->endEditCommand();
728  mLayer->triggerRepaint();
729  mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Attribute changes for multiple features applied." ), Qgis::Success, -1 );
730  }
731  else
732  {
733  mLayer->destroyEditCommand();
734  mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Changes could not be applied." ), Qgis::Warning, 0 );
735  }
736 
737  if ( !mButtonBox->isVisible() )
738  mMessageBar->pushItem( mMultiEditMessageBarItem );
739  return success;
740 }
741 
743 {
744  return saveWithDetails( nullptr );
745 }
746 
747 bool QgsAttributeForm::saveWithDetails( QString *error )
748 {
749  if ( error )
750  error->clear();
751 
752  if ( mIsSaving )
753  return true;
754 
755  for ( QgsWidgetWrapper *wrapper : qgis::as_const( mWidgets ) )
756  {
757  wrapper->notifyAboutToSave();
758  }
759 
760  // only do the dirty checks when editing an existing feature - for new
761  // features we need to add them even if the attributes are unchanged from the initial
762  // default values
763  switch ( mMode )
764  {
769  if ( !mDirty )
770  return true;
771  break;
772 
776  break;
777  }
778 
779  mIsSaving = true;
780 
781  bool success = true;
782 
783  emit beforeSave( success );
784 
785  // Somebody wants to prevent this form from saving
786  if ( !success )
787  return false;
788 
789  switch ( mMode )
790  {
797  success = saveEdits( error );
798  break;
799 
801  success = saveMultiEdits();
802  break;
803  }
804 
805  mIsSaving = false;
806  mUnsavedMultiEditChanges = false;
807  mDirty = false;
808 
809  return success;
810 }
811 
812 
814 {
815  mValuesInitialized = false;
816  const auto constMWidgets = mWidgets;
817  for ( QgsWidgetWrapper *ww : constMWidgets )
818  {
819  ww->setFeature( mFeature );
820  }
821  mValuesInitialized = true;
822  mDirty = false;
823 }
824 
826 {
827  const auto widgets { findChildren< QgsAttributeFormEditorWidget * >() };
828  for ( QgsAttributeFormEditorWidget *w : widgets )
829  {
830  w->resetSearch();
831  }
832 }
833 
834 void QgsAttributeForm::clearMultiEditMessages()
835 {
836  if ( mMultiEditUnsavedMessageBarItem )
837  {
838  if ( !mButtonBox->isVisible() )
839  mMessageBar->popWidget( mMultiEditUnsavedMessageBarItem );
840  mMultiEditUnsavedMessageBarItem = nullptr;
841  }
842  if ( mMultiEditMessageBarItem )
843  {
844  if ( !mButtonBox->isVisible() )
845  mMessageBar->popWidget( mMultiEditMessageBarItem );
846  mMultiEditMessageBarItem = nullptr;
847  }
848 }
849 
850 QString QgsAttributeForm::createFilterExpression() const
851 {
852  QStringList filters;
853  for ( QgsAttributeFormWidget *w : qgis::as_const( mFormWidgets ) )
854  {
855  QString filter = w->currentFilterExpression();
856  if ( !filter.isEmpty() )
857  filters << filter;
858  }
859 
860  if ( filters.isEmpty() )
861  return QString();
862 
863  QString filter = filters.join( QLatin1String( ") AND (" ) ).prepend( '(' ).append( ')' );
864  return filter;
865 }
866 
867 QgsExpressionContext QgsAttributeForm::createExpressionContext( const QgsFeature &feature ) const
868 {
869  QgsExpressionContext context;
872  if ( mExtraContextScope )
873  context.appendScope( new QgsExpressionContextScope( *mExtraContextScope.get() ) );
874  context.setFeature( feature );
875  return context;
876 }
877 
878 
879 void QgsAttributeForm::onAttributeChanged( const QVariant &value, const QVariantList &additionalFieldValues )
880 {
881  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
882  Q_ASSERT( eww );
883 
884  bool signalEmitted = false;
885 
886  if ( mValuesInitialized )
887  mDirty = true;
888 
889  mCurrentFormFeature.setAttribute( eww->field().name(), value );
890 
891  switch ( mMode )
892  {
897  {
899  emit attributeChanged( eww->field().name(), value );
901  emit widgetValueChanged( eww->field().name(), value, !mIsSettingFeature );
902 
903  // also emit the signal for additional values
904  const QStringList additionalFields = eww->additionalFields();
905  for ( int i = 0; i < additionalFields.count(); i++ )
906  {
907  const QString fieldName = additionalFields.at( i );
908  const QVariant value = additionalFieldValues.at( i );
909  emit widgetValueChanged( fieldName, value, !mIsSettingFeature );
910  }
911 
912  signalEmitted = true;
913 
914  updateJoinedFields( *eww );
915 
916  break;
917  }
919  {
920  if ( !mIsSettingMultiEditFeatures )
921  {
922  mUnsavedMultiEditChanges = true;
923 
924  QLabel *msgLabel = new QLabel( tr( "Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
925  msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
926  msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
927  connect( msgLabel, &QLabel::linkActivated, this, &QgsAttributeForm::multiEditMessageClicked );
928  clearMultiEditMessages();
929 
930  mMultiEditUnsavedMessageBarItem = new QgsMessageBarItem( msgLabel, Qgis::Warning );
931  if ( !mButtonBox->isVisible() )
932  mMessageBar->pushItem( mMultiEditUnsavedMessageBarItem );
933 
934  emit widgetValueChanged( eww->field().name(), value, !mIsSettingFeature );
935  }
936  break;
937  }
940  //nothing to do
941  break;
942  }
943 
944  updateConstraints( eww );
945 
946  //append field index here, so it's not updated recursive
947  mAlreadyUpdatedFields.append( eww->fieldIdx() );
948  updateDefaultValues( eww->fieldIdx() );
949  mAlreadyUpdatedFields.removeAll( eww->fieldIdx() );
950 
951  // Updates expression controlled labels
952  updateLabels();
953 
954  if ( !signalEmitted )
955  {
957  emit attributeChanged( eww->field().name(), value );
959  emit widgetValueChanged( eww->field().name(), value, !mIsSettingFeature );
960  }
961 }
962 
963 void QgsAttributeForm::updateAllConstraints()
964 {
965  const auto constMWidgets = mWidgets;
966  for ( QgsWidgetWrapper *ww : constMWidgets )
967  {
968  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
969  if ( eww )
970  updateConstraints( eww );
971  }
972 }
973 
974 void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww )
975 {
976  // get the current feature set in the form
977  QgsFeature ft;
978  if ( currentFormValuesFeature( ft ) )
979  {
980  // if the layer is NOT being edited then we only check layer based constraints, and not
981  // any constraints enforced by the provider. Because:
982  // 1. we want to keep browsing features nice and responsive. It's nice to give feedback as to whether
983  // the value checks out, but not if it's too slow to do so. Some constraints (e.g., unique) can be
984  // expensive to test. A user can freely remove a layer-based constraint if it proves to be too slow
985  // to test, but they are unlikely to have any control over provider-side constraints
986  // 2. the provider has already accepted the value, so presumably it doesn't violate the constraint
987  // and there's no point rechecking!
988 
989  // update eww constraint
990  updateConstraint( ft, eww );
991 
992  // update eww dependencies constraint
993  const QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
994 
995  for ( QgsEditorWidgetWrapper *depsEww : deps )
996  updateConstraint( ft, depsEww );
997 
998  // sync OK button status
999  synchronizeEnabledState();
1000 
1001  QgsExpressionContext context = createExpressionContext( ft );
1002 
1003  // Recheck visibility for all containers which are controlled by this value
1004  const QVector<ContainerInformation *> infos = mContainerInformationDependency.value( eww->field().name() );
1005  for ( ContainerInformation *info : infos )
1006  {
1007  info->apply( &context );
1008  }
1009  }
1010 }
1011 
1012 void QgsAttributeForm::updateContainersVisibility()
1013 {
1014  QgsExpressionContext context = createExpressionContext( mFeature );
1015 
1016  const QVector<ContainerInformation *> infos = mContainerVisibilityInformation;
1017 
1018  for ( ContainerInformation *info : infos )
1019  {
1020  info->apply( &context );
1021  }
1022 
1023  //and update the constraints
1024  updateAllConstraints();
1025 }
1026 
1027 void QgsAttributeForm::updateConstraint( const QgsFeature &ft, QgsEditorWidgetWrapper *eww )
1028 {
1029 
1030  if ( mContext.attributeFormMode() != QgsAttributeEditorContext::Mode::MultiEditMode )
1031  {
1032 
1034 
1035  if ( eww->layer()->fields().fieldOrigin( eww->fieldIdx() ) == QgsFields::OriginJoin )
1036  {
1037  int srcFieldIdx;
1038  const QgsVectorLayerJoinInfo *info = eww->layer()->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), eww->layer()->fields(), srcFieldIdx );
1039 
1040  if ( info && info->joinLayer() && info->isDynamicFormEnabled() )
1041  {
1042  if ( mJoinedFeatures.contains( info ) )
1043  {
1044  eww->updateConstraint( info->joinLayer(), srcFieldIdx, mJoinedFeatures[info], constraintOrigin );
1045  return;
1046  }
1047  else // if we are here, it means there's not joined field for this feature
1048  {
1049  eww->updateConstraint( QgsFeature() );
1050  return;
1051  }
1052  }
1053  }
1054  // default constraint update
1055  eww->updateConstraint( ft, constraintOrigin );
1056  }
1057 
1058 }
1059 
1060 void QgsAttributeForm::updateLabels()
1061 {
1062  if ( ! mLabelDataDefinedProperties.isEmpty() )
1063  {
1064  QgsFeature currentFeature;
1065  if ( currentFormValuesFeature( currentFeature ) )
1066  {
1067  QgsExpressionContext context = createExpressionContext( currentFeature );
1068 
1069  for ( auto it = mLabelDataDefinedProperties.constBegin() ; it != mLabelDataDefinedProperties.constEnd(); ++it )
1070  {
1071  QLabel *label { it.key() };
1072  bool ok;
1073  const QString value { it->valueAsString( context, QString(), &ok ) };
1074  if ( ok && ! value.isEmpty() )
1075  {
1076  label->setText( value );
1077  }
1078  }
1079  }
1080  }
1081 }
1082 
1083 bool QgsAttributeForm::currentFormValuesFeature( QgsFeature &feature )
1084 {
1085  bool rc = true;
1086  feature = QgsFeature( mFeature );
1088 
1089  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
1090  {
1091  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1092 
1093  if ( !eww )
1094  continue;
1095 
1096  if ( dst.count() > eww->fieldIdx() )
1097  {
1098  QVariantList dstVars = QVariantList() << dst.at( eww->fieldIdx() );
1099  QVariantList srcVars = QVariantList() << eww->value();
1100  QList<int> fieldIndexes = QList<int>() << eww->fieldIdx();
1101 
1102  // append additional fields
1103  const QStringList additionalFields = eww->additionalFields();
1104  for ( const QString &fieldName : additionalFields )
1105  {
1106  int idx = eww->layer()->fields().lookupField( fieldName );
1107  fieldIndexes << idx;
1108  dstVars << dst.at( idx );
1109  }
1110  srcVars.append( eww->additionalFieldValues() );
1111 
1112  Q_ASSERT( dstVars.count() == srcVars.count() );
1113 
1114  for ( int i = 0; i < dstVars.count(); i++ )
1115  {
1116  // need to check dstVar.isNull() != srcVar.isNull()
1117  // otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
1118  if ( ( !qgsVariantEqual( dstVars[i], srcVars[i] ) || dstVars[i].isNull() != srcVars[i].isNull() ) && srcVars[i].isValid() )
1119  {
1120  dst[fieldIndexes[i]] = srcVars[i];
1121  }
1122  }
1123  }
1124  else
1125  {
1126  rc = false;
1127  break;
1128  }
1129  }
1130 
1131  feature.setAttributes( dst );
1132 
1133  return rc;
1134 }
1135 
1136 
1137 void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
1138 {
1139  mContainerVisibilityInformation.append( info );
1140 
1141  const QSet<QString> referencedColumns = info->expression.referencedColumns();
1142 
1143  for ( const QString &col : referencedColumns )
1144  {
1145  mContainerInformationDependency[ col ].append( info );
1146  }
1147 }
1148 
1149 bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions )
1150 {
1151  bool valid( true );
1152 
1153  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
1154  {
1155  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1156  if ( eww )
1157  {
1158  if ( ! eww->isValidConstraint() )
1159  {
1160  invalidFields.append( eww->field().displayName() );
1161 
1162  descriptions.append( eww->constraintFailureReason() );
1163 
1164  if ( eww->isBlockingCommit() )
1165  valid = false; // continue to get all invalid fields
1166  }
1167  }
1168  }
1169 
1170  return valid;
1171 }
1172 
1173 void QgsAttributeForm::onAttributeAdded( int idx )
1174 {
1175  mPreventFeatureRefresh = false;
1176  if ( mFeature.isValid() )
1177  {
1178  QgsAttributes attrs = mFeature.attributes();
1179  attrs.insert( idx, QVariant( layer()->fields().at( idx ).type() ) );
1180  mFeature.setFields( layer()->fields() );
1181  mFeature.setAttributes( attrs );
1182  }
1183  init();
1184  setFeature( mFeature );
1185 }
1186 
1187 void QgsAttributeForm::onAttributeDeleted( int idx )
1188 {
1189  mPreventFeatureRefresh = false;
1190  if ( mFeature.isValid() )
1191  {
1192  QgsAttributes attrs = mFeature.attributes();
1193  attrs.remove( idx );
1194  mFeature.setFields( layer()->fields() );
1195  mFeature.setAttributes( attrs );
1196  }
1197  init();
1198  setFeature( mFeature );
1199 }
1200 
1201 void QgsAttributeForm::onUpdatedFields()
1202 {
1203  mPreventFeatureRefresh = false;
1204  if ( mFeature.isValid() )
1205  {
1206  QgsAttributes attrs( layer()->fields().size() );
1207  for ( int i = 0; i < layer()->fields().size(); i++ )
1208  {
1209  int idx = mFeature.fields().indexFromName( layer()->fields().at( i ).name() );
1210  if ( idx != -1 )
1211  {
1212  attrs[i] = mFeature.attributes().at( idx );
1213  if ( mFeature.attributes().at( idx ).type() != layer()->fields().at( i ).type() )
1214  {
1215  attrs[i].convert( layer()->fields().at( i ).type() );
1216  }
1217  }
1218  else
1219  {
1220  attrs[i] = QVariant( layer()->fields().at( i ).type() );
1221  }
1222  }
1223  mFeature.setFields( layer()->fields() );
1224  mFeature.setAttributes( attrs );
1225  }
1226  init();
1227  setFeature( mFeature );
1228 }
1229 
1230 void QgsAttributeForm::onConstraintStatusChanged( const QString &constraint,
1231  const QString &description, const QString &err, QgsEditorWidgetWrapper::ConstraintResult result )
1232 {
1233  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
1234  Q_ASSERT( eww );
1235 
1236  QgsAttributeFormEditorWidget *formEditorWidget = mFormEditorWidgets.value( eww->fieldIdx() );
1237 
1238  if ( formEditorWidget )
1239  formEditorWidget->setConstraintStatus( constraint, description, err, result );
1240 }
1241 
1242 QList<QgsEditorWidgetWrapper *> QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper *w )
1243 {
1244  QList<QgsEditorWidgetWrapper *> wDeps;
1245  QString name = w->field().name();
1246 
1247  // for each widget in the current form
1248  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
1249  {
1250  // get the wrapper
1251  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1252  if ( eww )
1253  {
1254  // compare name to not compare w to itself
1255  QString ewwName = eww->field().name();
1256  if ( name != ewwName )
1257  {
1258  // get expression and referencedColumns
1259  QgsExpression expr = eww->layer()->fields().at( eww->fieldIdx() ).constraints().constraintExpression();
1260 
1261  const auto referencedColumns = expr.referencedColumns();
1262 
1263  for ( const QString &colName : referencedColumns )
1264  {
1265  if ( name == colName )
1266  {
1267  wDeps.append( eww );
1268  break;
1269  }
1270  }
1271  }
1272  }
1273  }
1274 
1275  return wDeps;
1276 }
1277 
1278 QgsRelationWidgetWrapper *QgsAttributeForm::setupRelationWidgetWrapper( const QgsRelation &rel, const QgsAttributeEditorContext &context )
1279 {
1280  return setupRelationWidgetWrapper( QString(), rel, context );
1281 }
1282 
1283 QgsRelationWidgetWrapper *QgsAttributeForm::setupRelationWidgetWrapper( const QString &relationWidgetTypeId, const QgsRelation &rel, const QgsAttributeEditorContext &context )
1284 {
1285  QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( relationWidgetTypeId, mLayer, rel, nullptr, this );
1286  const QVariantMap config = mLayer->editFormConfig().widgetConfig( rel.id() );
1287  rww->setConfig( config );
1288  rww->setContext( context );
1289 
1290  return rww;
1291 }
1292 
1293 void QgsAttributeForm::preventFeatureRefresh()
1294 {
1295  mPreventFeatureRefresh = true;
1296 }
1297 
1299 {
1300  if ( mPreventFeatureRefresh || mLayer->isEditable() || !mFeature.isValid() )
1301  return;
1302 
1303  // reload feature if layer changed although not editable
1304  // (datasource probably changed bypassing QgsVectorLayer)
1305  if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeature.id() ) ).nextFeature( mFeature ) )
1306  return;
1307 
1308  init();
1309  setFeature( mFeature );
1310 }
1311 
1312 void QgsAttributeForm::parentFormValueChanged( const QString &attribute, const QVariant &newValue )
1313 {
1314  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
1315  {
1316  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1317  if ( eww )
1318  {
1319  eww->parentFormValueChanged( attribute, newValue );
1320  }
1321  }
1322 }
1323 
1324 void QgsAttributeForm::synchronizeEnabledState()
1325 {
1326  bool isEditable = ( mFeature.isValid()
1328  || mMode == QgsAttributeEditorContext::MultiEditMode ) && mLayer->isEditable();
1329 
1330  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
1331  {
1332  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1333  if ( eww )
1334  {
1335  QgsAttributeFormEditorWidget *formWidget = mFormEditorWidgets.value( eww->fieldIdx() );
1336 
1337  if ( formWidget )
1338  formWidget->setConstraintResultVisible( isEditable );
1339 
1340  eww->setConstraintResultVisible( isEditable );
1341 
1342  bool enabled = isEditable && fieldIsEditable( eww->fieldIdx() );
1343  ww->setEnabled( enabled );
1344 
1345  updateIcon( eww );
1346  }
1347  }
1348 
1350  {
1351  QStringList invalidFields, descriptions;
1352  bool validConstraint = currentFormValidConstraints( invalidFields, descriptions );
1353 
1354  isEditable = isEditable & validConstraint;
1355  }
1356 
1357  // change OK button status
1358  QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1359  if ( okButton )
1360  okButton->setEnabled( isEditable );
1361 }
1362 
1363 void QgsAttributeForm::init()
1364 {
1365  QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1366 
1367  // Cleanup of any previously shown widget, we start from scratch
1368  QWidget *formWidget = nullptr;
1369 
1370  bool buttonBoxVisible = true;
1371  // Cleanup button box but preserve visibility
1372  if ( mButtonBox )
1373  {
1374  buttonBoxVisible = mButtonBox->isVisible();
1375  delete mButtonBox;
1376  mButtonBox = nullptr;
1377  }
1378 
1379  if ( mSearchButtonBox )
1380  {
1381  delete mSearchButtonBox;
1382  mSearchButtonBox = nullptr;
1383  }
1384 
1385  qDeleteAll( mWidgets );
1386  mWidgets.clear();
1387 
1388  while ( QWidget *w = this->findChild<QWidget *>() )
1389  {
1390  delete w;
1391  }
1392  delete layout();
1393 
1394  QVBoxLayout *vl = new QVBoxLayout();
1395  vl->setContentsMargins( 0, 0, 0, 0 );
1396  mMessageBar = new QgsMessageBar( this );
1397  mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1398  vl->addWidget( mMessageBar );
1399 
1400  setLayout( vl );
1401 
1402  // Get a layout
1403  QGridLayout *layout = new QGridLayout();
1404  QWidget *container = new QWidget();
1405  container->setLayout( layout );
1406  vl->addWidget( container );
1407 
1408  mFormEditorWidgets.clear();
1409  mFormWidgets.clear();
1410 
1411  // a bar to warn the user with non-blocking messages
1412  setContentsMargins( 0, 0, 0, 0 );
1413 
1414  // Try to load Ui-File for layout
1415  if ( mContext.allowCustomUi() && mLayer->editFormConfig().layout() == QgsEditFormConfig::UiFileLayout &&
1416  !mLayer->editFormConfig().uiForm().isEmpty() )
1417  {
1418  QgsDebugMsg( QStringLiteral( "loading form: %1" ).arg( mLayer->editFormConfig().uiForm() ) );
1419  const QString path = mLayer->editFormConfig().uiForm();
1421  if ( file && file->open( QFile::ReadOnly ) )
1422  {
1423  QUiLoader loader;
1424 
1425  QFileInfo fi( file->fileName() );
1426  loader.setWorkingDirectory( fi.dir() );
1427  formWidget = loader.load( file, this );
1428  if ( formWidget )
1429  {
1430  formWidget->setWindowFlags( Qt::Widget );
1431  layout->addWidget( formWidget );
1432  formWidget->show();
1433  file->close();
1434  mButtonBox = findChild<QDialogButtonBox *>();
1435  createWrappers();
1436 
1437  formWidget->installEventFilter( this );
1438  }
1439  }
1440  }
1441 
1442  QgsTabWidget *tabWidget = nullptr;
1443 
1444  // Tab layout
1445  if ( !formWidget && mLayer->editFormConfig().layout() == QgsEditFormConfig::TabLayout )
1446  {
1447  int row = 0;
1448  int column = 0;
1449  int columnCount = 1;
1450 
1451  const QList<QgsAttributeEditorElement *> tabs = mLayer->editFormConfig().tabs();
1452 
1453  for ( QgsAttributeEditorElement *widgDef : tabs )
1454  {
1455  if ( widgDef->type() == QgsAttributeEditorElement::AeTypeContainer )
1456  {
1457  QgsAttributeEditorContainer *containerDef = dynamic_cast<QgsAttributeEditorContainer *>( widgDef );
1458  if ( !containerDef )
1459  continue;
1460 
1461  if ( containerDef->isGroupBox() )
1462  {
1463  tabWidget = nullptr;
1464  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1465  layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1466  if ( containerDef->visibilityExpression().enabled() )
1467  {
1468  registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().data() ) );
1469  }
1470  column += 2;
1471  }
1472  else
1473  {
1474  if ( !tabWidget )
1475  {
1476  tabWidget = new QgsTabWidget();
1477  layout->addWidget( tabWidget, row, column, 1, 2 );
1478  column += 2;
1479  }
1480 
1481  QWidget *tabPage = new QWidget( tabWidget );
1482 
1483  tabWidget->addTab( tabPage, widgDef->name() );
1484 
1485  if ( containerDef->visibilityExpression().enabled() )
1486  {
1487  registerContainerInformation( new ContainerInformation( tabWidget, tabPage, containerDef->visibilityExpression().data() ) );
1488  }
1489  QGridLayout *tabPageLayout = new QGridLayout();
1490  tabPage->setLayout( tabPageLayout );
1491 
1492  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1493  tabPageLayout->addWidget( widgetInfo.widget );
1494  }
1495  }
1496  else
1497  {
1498  tabWidget = nullptr;
1499  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1500  QLabel *label = new QLabel( widgetInfo.labelText );
1501  label->setToolTip( widgetInfo.toolTip );
1502  if ( columnCount > 1 && !widgetInfo.labelOnTop )
1503  {
1504  label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1505  }
1506 
1507  label->setBuddy( widgetInfo.widget );
1508 
1509  if ( !widgetInfo.showLabel )
1510  {
1511  QVBoxLayout *c = new QVBoxLayout();
1512  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1513  c->addWidget( widgetInfo.widget );
1514  layout->addLayout( c, row, column, 1, 2 );
1515  column += 2;
1516  }
1517  else if ( widgetInfo.labelOnTop )
1518  {
1519  QVBoxLayout *c = new QVBoxLayout();
1520  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1521  c->addWidget( label );
1522  c->addWidget( widgetInfo.widget );
1523  layout->addLayout( c, row, column, 1, 2 );
1524  column += 2;
1525  }
1526  else
1527  {
1528  layout->addWidget( label, row, column++ );
1529  layout->addWidget( widgetInfo.widget, row, column++ );
1530  }
1531 
1532  // Alias DD overrides
1533  if ( widgDef->type() == QgsAttributeEditorElement::AttributeEditorType::AeTypeField )
1534  {
1535  const QgsAttributeEditorField *fieldElement { static_cast<QgsAttributeEditorField *>( widgDef ) };
1536  const int fieldIdx = fieldElement->idx();
1537  if ( fieldIdx >= 0 && fieldIdx < mLayer->fields().count() )
1538  {
1539  const QString fieldName { mLayer->fields().at( fieldIdx ).name() };
1540  if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
1541  {
1542  const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
1543  if ( property.isActive() && ! property.expressionString().isEmpty() )
1544  {
1545  mLabelDataDefinedProperties[ label ] = property;
1546  }
1547  }
1548  }
1549  }
1550  }
1551 
1552  if ( column >= columnCount * 2 )
1553  {
1554  column = 0;
1555  row += 1;
1556  }
1557  }
1558  formWidget = container;
1559  }
1560 
1561  // Autogenerate Layout
1562  // If there is still no layout loaded (defined as autogenerate or other methods failed)
1563  mIconMap.clear();
1564 
1565  if ( !formWidget )
1566  {
1567  formWidget = new QWidget( this );
1568  QGridLayout *gridLayout = new QGridLayout( formWidget );
1569  formWidget->setLayout( gridLayout );
1570 
1571  if ( mContext.formMode() != QgsAttributeEditorContext::Embed )
1572  {
1573  // put the form into a scroll area to nicely handle cases with lots of attributes
1574  QgsScrollArea *scrollArea = new QgsScrollArea( this );
1575  scrollArea->setWidget( formWidget );
1576  scrollArea->setWidgetResizable( true );
1577  scrollArea->setFrameShape( QFrame::NoFrame );
1578  scrollArea->setFrameShadow( QFrame::Plain );
1579  scrollArea->setFocusProxy( this );
1580  layout->addWidget( scrollArea );
1581  }
1582  else
1583  {
1584  layout->addWidget( formWidget );
1585  }
1586 
1587  int row = 0;
1588 
1589  const QgsFields fields = mLayer->fields();
1590 
1591  for ( const QgsField &field : fields )
1592  {
1593  int idx = fields.lookupField( field.name() );
1594  if ( idx < 0 )
1595  continue;
1596 
1597  //show attribute alias if available
1598  QString fieldName = mLayer->attributeDisplayName( idx );
1599  QString labelText = fieldName;
1600  labelText.replace( '&', QLatin1String( "&&" ) ); // need to escape '&' or they'll be replace by _ in the label text
1601 
1602  const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, field.name() );
1603 
1604  if ( widgetSetup.type() == QLatin1String( "Hidden" ) )
1605  continue;
1606 
1607  bool labelOnTop = mLayer->editFormConfig().labelOnTop( idx );
1608 
1609  // This will also create the widget
1610  QLabel *label = new QLabel( labelText );
1611  label->setToolTip( QgsFieldModel::fieldToolTipExtended( field, mLayer ) );
1612  QSvgWidget *i = new QSvgWidget();
1613  i->setFixedSize( 18, 18 );
1614 
1615  if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
1616  {
1617  const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
1618  if ( property.isActive() && ! property.expressionString().isEmpty() )
1619  {
1620  mLabelDataDefinedProperties[ label ] = property;
1621  }
1622  }
1623 
1624  QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, idx, widgetSetup.config(), nullptr, this, mContext );
1625 
1626  QWidget *w = nullptr;
1627  if ( eww )
1628  {
1629  QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
1630  w = formWidget;
1631  mFormEditorWidgets.insert( idx, formWidget );
1632  mFormWidgets.append( formWidget );
1633  formWidget->createSearchWidgetWrappers( mContext );
1634 
1635  label->setBuddy( eww->widget() );
1636  }
1637  else
1638  {
1639  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() ) ) );
1640  }
1641 
1642 
1643  if ( w )
1644  w->setObjectName( field.name() );
1645 
1646  if ( eww )
1647  {
1648  addWidgetWrapper( eww );
1649  mIconMap[eww->widget()] = i;
1650  }
1651 
1652  if ( labelOnTop )
1653  {
1654  gridLayout->addWidget( label, row++, 0, 1, 2 );
1655  gridLayout->addWidget( w, row++, 0, 1, 2 );
1656  gridLayout->addWidget( i, row++, 0, 1, 2 );
1657  }
1658  else
1659  {
1660  gridLayout->addWidget( label, row, 0 );
1661  gridLayout->addWidget( w, row, 1 );
1662  gridLayout->addWidget( i, row++, 2 );
1663  }
1664 
1665  }
1666 
1667  const QList<QgsRelation> relations = QgsProject::instance()->relationManager()->referencedRelations( mLayer );
1668  for ( const QgsRelation &rel : relations )
1669  {
1670  QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( QStringLiteral( "relation_editor" ), rel, mContext );
1671 
1673  formWidget->createSearchWidgetWrappers( mContext );
1674  gridLayout->addWidget( formWidget, row++, 0, 1, 2 );
1675 
1676  mWidgets.append( rww );
1677  mFormWidgets.append( formWidget );
1678  }
1679 
1680  if ( QgsProject::instance()->relationManager()->referencedRelations( mLayer ).isEmpty() )
1681  {
1682  QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1683  gridLayout->addItem( spacerItem, row, 0 );
1684  gridLayout->setRowStretch( row, 1 );
1685  row++;
1686  }
1687  }
1688 
1689  updateDefaultValueDependencies();
1690 
1691  if ( !mButtonBox )
1692  {
1693  mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1694  mButtonBox->setObjectName( QStringLiteral( "buttonBox" ) );
1695  layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
1696  }
1697  mButtonBox->setVisible( buttonBoxVisible );
1698 
1699  if ( !mSearchButtonBox )
1700  {
1701  mSearchButtonBox = new QWidget();
1702  QHBoxLayout *boxLayout = new QHBoxLayout();
1703  boxLayout->setContentsMargins( 0, 0, 0, 0 );
1704  mSearchButtonBox->setLayout( boxLayout );
1705  mSearchButtonBox->setObjectName( QStringLiteral( "searchButtonBox" ) );
1706 
1707  QPushButton *clearButton = new QPushButton( tr( "&Reset Form" ), mSearchButtonBox );
1708  connect( clearButton, &QPushButton::clicked, this, &QgsAttributeForm::resetSearch );
1709  boxLayout->addWidget( clearButton );
1710  boxLayout->addStretch( 1 );
1711 
1712  QPushButton *flashButton = new QPushButton();
1713  flashButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1714  flashButton->setText( tr( "&Flash Features" ) );
1715  connect( flashButton, &QToolButton::clicked, this, &QgsAttributeForm::searchFlash );
1716  boxLayout->addWidget( flashButton );
1717 
1718  QPushButton *zoomButton = new QPushButton();
1719  zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1720  zoomButton->setText( tr( "&Zoom to Features" ) );
1721  connect( zoomButton, &QToolButton::clicked, this, &QgsAttributeForm::searchZoomTo );
1722  boxLayout->addWidget( zoomButton );
1723 
1724  QToolButton *selectButton = new QToolButton();
1725  selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1726  selectButton->setText( tr( "&Select Features" ) );
1727  selectButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
1728  selectButton->setPopupMode( QToolButton::MenuButtonPopup );
1729  selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
1730  connect( selectButton, &QToolButton::clicked, this, &QgsAttributeForm::searchSetSelection );
1731  QMenu *selectMenu = new QMenu( selectButton );
1732  QAction *selectAction = new QAction( tr( "Select Features" ), selectMenu );
1733  selectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
1734  connect( selectAction, &QAction::triggered, this, &QgsAttributeForm::searchSetSelection );
1735  selectMenu->addAction( selectAction );
1736  QAction *addSelectAction = new QAction( tr( "Add to Current Selection" ), selectMenu );
1737  addSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectAdd.svg" ) ) );
1738  connect( addSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchAddToSelection );
1739  selectMenu->addAction( addSelectAction );
1740  QAction *deselectAction = new QAction( tr( "Remove from Current Selection" ), selectMenu );
1741  deselectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectRemove.svg" ) ) );
1742  connect( deselectAction, &QAction::triggered, this, &QgsAttributeForm::searchRemoveFromSelection );
1743  selectMenu->addAction( deselectAction );
1744  QAction *filterSelectAction = new QAction( tr( "Filter Current Selection" ), selectMenu );
1745  filterSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectIntersect.svg" ) ) );
1746  connect( filterSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchIntersectSelection );
1747  selectMenu->addAction( filterSelectAction );
1748  selectButton->setMenu( selectMenu );
1749  boxLayout->addWidget( selectButton );
1750 
1751  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
1752  {
1753  QToolButton *filterButton = new QToolButton();
1754  filterButton->setText( tr( "Filter Features" ) );
1755  filterButton->setPopupMode( QToolButton::MenuButtonPopup );
1756  filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1757  connect( filterButton, &QToolButton::clicked, this, &QgsAttributeForm::filterTriggered );
1758  QMenu *filterMenu = new QMenu( filterButton );
1759  QAction *filterAndAction = new QAction( tr( "Filter Within (\"AND\")" ), filterMenu );
1760  connect( filterAndAction, &QAction::triggered, this, &QgsAttributeForm::filterAndTriggered );
1761  filterMenu->addAction( filterAndAction );
1762  QAction *filterOrAction = new QAction( tr( "Extend Filter (\"OR\")" ), filterMenu );
1763  connect( filterOrAction, &QAction::triggered, this, &QgsAttributeForm::filterOrTriggered );
1764  filterMenu->addAction( filterOrAction );
1765  filterButton->setMenu( filterMenu );
1766  boxLayout->addWidget( filterButton );
1767  }
1768  else
1769  {
1770  QPushButton *closeButton = new QPushButton( tr( "Close" ), mSearchButtonBox );
1771  connect( closeButton, &QPushButton::clicked, this, &QgsAttributeForm::closed );
1772  closeButton->setShortcut( Qt::Key_Escape );
1773  boxLayout->addWidget( closeButton );
1774  }
1775 
1776  layout->addWidget( mSearchButtonBox );
1777  }
1778  mSearchButtonBox->setVisible( mMode == QgsAttributeEditorContext::SearchMode );
1779 
1780  afterWidgetInit();
1781 
1782  connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
1783  connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
1784 
1785  connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsAttributeForm::synchronizeEnabledState );
1786  connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsAttributeForm::synchronizeEnabledState );
1787 
1788  // This triggers a refresh of the form widget and gives a chance to re-format the
1789  // value to those widgets that have a different representation when in edit mode
1792 
1793 
1794  const auto constMInterfaces = mInterfaces;
1795  for ( QgsAttributeFormInterface *iface : constMInterfaces )
1796  {
1797  iface->initForm();
1798  }
1799 
1801  {
1802  hideButtonBox();
1803  }
1804 
1805  QApplication::restoreOverrideCursor();
1806 }
1807 
1808 void QgsAttributeForm::cleanPython()
1809 {
1810  if ( !mPyFormVarName.isNull() )
1811  {
1812  QString expr = QStringLiteral( "if '%1' in locals(): del %1\n" ).arg( mPyFormVarName );
1813  QgsPythonRunner::run( expr );
1814  }
1815 }
1816 
1817 void QgsAttributeForm::initPython()
1818 {
1819  cleanPython();
1820 
1821  // Init Python, if init function is not empty and the combo indicates
1822  // the source for the function code
1823  if ( !mLayer->editFormConfig().initFunction().isEmpty()
1824  && mLayer->editFormConfig().initCodeSource() != QgsEditFormConfig::CodeSourceNone )
1825  {
1826 
1827  QString initFunction = mLayer->editFormConfig().initFunction();
1828  QString initFilePath = mLayer->editFormConfig().initFilePath();
1829  QString initCode;
1830 
1831  switch ( mLayer->editFormConfig().initCodeSource() )
1832  {
1833  case QgsEditFormConfig::CodeSourceFile:
1834  if ( !initFilePath.isEmpty() )
1835  {
1836  QFile *inputFile = QgsApplication::instance()->networkContentFetcherRegistry()->localFile( initFilePath );
1837 
1838  if ( inputFile && inputFile->open( QFile::ReadOnly ) )
1839  {
1840  // Read it into a string
1841  QTextStream inf( inputFile );
1842  initCode = inf.readAll();
1843  inputFile->close();
1844  }
1845  else // The file couldn't be opened
1846  {
1847  QgsLogger::warning( QStringLiteral( "The external python file path %1 could not be opened!" ).arg( initFilePath ) );
1848  }
1849  }
1850  else
1851  {
1852  QgsLogger::warning( QStringLiteral( "The external python file path is empty!" ) );
1853  }
1854  break;
1855 
1856  case QgsEditFormConfig::CodeSourceDialog:
1857  initCode = mLayer->editFormConfig().initCode();
1858  if ( initCode.isEmpty() )
1859  {
1860  QgsLogger::warning( QStringLiteral( "The python code provided in the dialog is empty!" ) );
1861  }
1862  break;
1863 
1864  case QgsEditFormConfig::CodeSourceEnvironment:
1865  case QgsEditFormConfig::CodeSourceNone:
1866  // Nothing to do: the function code should be already in the environment
1867  break;
1868  }
1869 
1870  // If we have a function code, run it
1871  if ( !initCode.isEmpty() )
1872  {
1874  QgsPythonRunner::run( initCode );
1875  else
1876  mMessageBar->pushMessage( QString(),
1877  tr( "Python macro could not be run due to missing permissions." ),
1878  Qgis::MessageLevel::Warning );
1879  }
1880 
1881  QgsPythonRunner::run( QStringLiteral( "import inspect" ) );
1882  QString numArgs;
1883 
1884  // Check for eval result
1885  if ( QgsPythonRunner::eval( QStringLiteral( "len(inspect.getfullargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
1886  {
1887  static int sFormId = 0;
1888  mPyFormVarName = QStringLiteral( "_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
1889 
1890  QString form = QStringLiteral( "%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
1891  .arg( mPyFormVarName )
1892  .arg( ( quint64 ) this );
1893 
1894  QgsPythonRunner::run( form );
1895 
1896  QgsDebugMsg( QStringLiteral( "running featureForm init: %1" ).arg( mPyFormVarName ) );
1897 
1898  // Legacy
1899  if ( numArgs == QLatin1String( "3" ) )
1900  {
1901  addInterface( new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName, this ) );
1902  }
1903  else
1904  {
1905  // If we get here, it means that the function doesn't accept three arguments
1906  QMessageBox msgBox;
1907  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 ) );
1908  msgBox.exec();
1909 #if 0
1910  QString expr = QString( "%1(%2)" )
1911  .arg( mLayer->editFormInit() )
1912  .arg( mPyFormVarName );
1913  QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr, "QgsAttributeFormInterface" );
1914  if ( iface )
1915  addInterface( iface );
1916 #endif
1917  }
1918  }
1919  else
1920  {
1921  // If we get here, it means that inspect couldn't find the function
1922  QMessageBox msgBox;
1923  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 ) );
1924  msgBox.exec();
1925  }
1926  }
1927 }
1928 
1929 QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement *widgetDef, QWidget *parent, QgsVectorLayer *vl, QgsAttributeEditorContext &context )
1930 {
1931  WidgetInfo newWidgetInfo;
1932 
1933  switch ( widgetDef->type() )
1934  {
1935  case QgsAttributeEditorElement::AeTypeField:
1936  {
1937  const QgsAttributeEditorField *fieldDef = dynamic_cast<const QgsAttributeEditorField *>( widgetDef );
1938  if ( !fieldDef )
1939  break;
1940 
1941  const QgsFields fields = vl->fields();
1942  int fldIdx = fields.lookupField( fieldDef->name() );
1943  if ( fldIdx < fields.count() && fldIdx >= 0 )
1944  {
1945  const QgsEditorWidgetSetup widgetSetup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fieldDef->name() );
1946 
1947  QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( widgetSetup.type(), mLayer, fldIdx, widgetSetup.config(), nullptr, this, mContext );
1948  QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, widgetSetup.type(), this );
1949  mFormEditorWidgets.insert( fldIdx, formWidget );
1950  mFormWidgets.append( formWidget );
1951 
1952  formWidget->createSearchWidgetWrappers( mContext );
1953 
1954  newWidgetInfo.widget = formWidget;
1955  addWidgetWrapper( eww );
1956 
1957  newWidgetInfo.widget->setObjectName( fields.at( fldIdx ).name() );
1958  newWidgetInfo.hint = fields.at( fldIdx ).comment();
1959  }
1960 
1961  newWidgetInfo.labelOnTop = mLayer->editFormConfig().labelOnTop( fldIdx );
1962  newWidgetInfo.labelText = mLayer->attributeDisplayName( fldIdx );
1963  newWidgetInfo.labelText.replace( '&', QLatin1String( "&&" ) ); // need to escape '&' or they'll be replace by _ in the label text
1964  newWidgetInfo.toolTip = QStringLiteral( "<b>%1</b><p>%2</p>" ).arg( mLayer->attributeDisplayName( fldIdx ), newWidgetInfo.hint );
1965  newWidgetInfo.showLabel = widgetDef->showLabel();
1966 
1967  break;
1968  }
1969 
1970  case QgsAttributeEditorElement::AeTypeRelation:
1971  {
1972  const QgsAttributeEditorRelation *relDef = static_cast<const QgsAttributeEditorRelation *>( widgetDef );
1973 
1974  QgsRelationWidgetWrapper *rww = setupRelationWidgetWrapper( relDef->relationWidgetTypeId(), relDef->relation(), context );
1975 
1977  formWidget->createSearchWidgetWrappers( mContext );
1978 
1979  // This needs to be after QgsAttributeFormRelationEditorWidget creation, because the widget
1980  // does not exists yet until QgsAttributeFormRelationEditorWidget is created and the setters
1981  // below directly alter the widget and check for it.
1982  rww->setWidgetConfig( relDef->relationEditorConfiguration() );
1983  rww->setShowLabel( relDef->showLabel() );
1984  rww->setNmRelationId( relDef->nmRelationId() );
1985  rww->setForceSuppressFormPopup( relDef->forceSuppressFormPopup() );
1986  rww->setLabel( relDef->label() );
1987 
1988  mWidgets.append( rww );
1989  mFormWidgets.append( formWidget );
1990 
1991  newWidgetInfo.widget = formWidget;
1992  newWidgetInfo.showLabel = relDef->showLabel();
1993  newWidgetInfo.labelText = QString();
1994  newWidgetInfo.labelOnTop = true;
1995  break;
1996  }
1997 
1998  case QgsAttributeEditorElement::AeTypeContainer:
1999  {
2000  const QgsAttributeEditorContainer *container = dynamic_cast<const QgsAttributeEditorContainer *>( widgetDef );
2001  if ( !container )
2002  break;
2003 
2004  int columnCount = container->columnCount();
2005 
2006  if ( columnCount <= 0 )
2007  columnCount = 1;
2008 
2009  QString widgetName;
2010  QWidget *myContainer = nullptr;
2011  if ( container->isGroupBox() )
2012  {
2013  QGroupBox *groupBox = new QGroupBox( parent );
2014  widgetName = QStringLiteral( "QGroupBox" );
2015  if ( container->showLabel() )
2016  groupBox->setTitle( container->name() );
2017  myContainer = groupBox;
2018  newWidgetInfo.widget = myContainer;
2019  }
2020  else
2021  {
2022  myContainer = new QWidget();
2023 
2024  if ( context.formMode() != QgsAttributeEditorContext::Embed )
2025  {
2026  QgsScrollArea *scrollArea = new QgsScrollArea( parent );
2027 
2028  scrollArea->setWidget( myContainer );
2029  scrollArea->setWidgetResizable( true );
2030  scrollArea->setFrameShape( QFrame::NoFrame );
2031  widgetName = QStringLiteral( "QScrollArea QWidget" );
2032 
2033  newWidgetInfo.widget = scrollArea;
2034  }
2035  else
2036  {
2037  newWidgetInfo.widget = myContainer;
2038  widgetName = QStringLiteral( "QWidget" );
2039  }
2040  }
2041 
2042  if ( container->backgroundColor().isValid() )
2043  {
2044  QString style {QStringLiteral( "background-color: %1;" ).arg( container->backgroundColor().name() )};
2045  newWidgetInfo.widget->setStyleSheet( style );
2046  }
2047 
2048  QGridLayout *gbLayout = new QGridLayout();
2049  myContainer->setLayout( gbLayout );
2050 
2051  int row = 0;
2052  int column = 0;
2053 
2054  const QList<QgsAttributeEditorElement *> children = container->children();
2055 
2056  for ( QgsAttributeEditorElement *childDef : children )
2057  {
2058  WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
2059 
2060  if ( childDef->type() == QgsAttributeEditorElement::AeTypeContainer )
2061  {
2062  QgsAttributeEditorContainer *containerDef = static_cast<QgsAttributeEditorContainer *>( childDef );
2063  if ( containerDef->visibilityExpression().enabled() )
2064  {
2065  registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().data() ) );
2066  }
2067  }
2068 
2069  if ( widgetInfo.labelText.isNull() )
2070  {
2071  gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
2072  column += 2;
2073  }
2074  else
2075  {
2076  QLabel *mypLabel = new QLabel( widgetInfo.labelText );
2077 
2078  // Alias DD overrides
2079  if ( childDef->type() == QgsAttributeEditorElement::AeTypeField )
2080  {
2081  const QgsAttributeEditorField *fieldDef { static_cast<QgsAttributeEditorField *>( childDef ) };
2082  const QgsFields fields = vl->fields();
2083  const int fldIdx = fieldDef->idx();
2084  if ( fldIdx < fields.count() && fldIdx >= 0 )
2085  {
2086  const QString fieldName { fields.at( fldIdx ).name() };
2087  if ( mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).hasProperty( QgsEditFormConfig::DataDefinedProperty::Alias ) )
2088  {
2089  const QgsProperty property { mLayer->editFormConfig().dataDefinedFieldProperties( fieldName ).property( QgsEditFormConfig::DataDefinedProperty::Alias ) };
2090  if ( property.isActive() && ! property.expressionString().isEmpty() )
2091  {
2092  mLabelDataDefinedProperties[ mypLabel ] = property;
2093  }
2094  }
2095  }
2096  }
2097 
2098  mypLabel->setToolTip( widgetInfo.toolTip );
2099  if ( columnCount > 1 && !widgetInfo.labelOnTop )
2100  {
2101  mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
2102  }
2103 
2104  mypLabel->setBuddy( widgetInfo.widget );
2105 
2106  if ( widgetInfo.labelOnTop )
2107  {
2108  QVBoxLayout *c = new QVBoxLayout();
2109  mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
2110  c->layout()->addWidget( mypLabel );
2111  c->layout()->addWidget( widgetInfo.widget );
2112  gbLayout->addLayout( c, row, column, 1, 2 );
2113  column += 2;
2114  }
2115  else
2116  {
2117  gbLayout->addWidget( mypLabel, row, column++ );
2118  gbLayout->addWidget( widgetInfo.widget, row, column++ );
2119  }
2120  }
2121 
2122  if ( column >= columnCount * 2 )
2123  {
2124  column = 0;
2125  row += 1;
2126  }
2127  }
2128  QWidget *spacer = new QWidget();
2129  spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
2130  gbLayout->addWidget( spacer, ++row, 0 );
2131  gbLayout->setRowStretch( row, 1 );
2132 
2133  newWidgetInfo.labelText = QString();
2134  newWidgetInfo.labelOnTop = true;
2135  break;
2136  }
2137 
2138  case QgsAttributeEditorElement::AeTypeQmlElement:
2139  {
2140  const QgsAttributeEditorQmlElement *elementDef = static_cast<const QgsAttributeEditorQmlElement *>( widgetDef );
2141 
2142  QgsQmlWidgetWrapper *qmlWrapper = new QgsQmlWidgetWrapper( mLayer, nullptr, this );
2143  qmlWrapper->setQmlCode( elementDef->qmlCode() );
2144  context.setAttributeFormMode( mMode );
2145  qmlWrapper->setContext( context );
2146 
2147  mWidgets.append( qmlWrapper );
2148 
2149  newWidgetInfo.widget = qmlWrapper->widget();
2150  newWidgetInfo.labelText = elementDef->name();
2151  newWidgetInfo.labelOnTop = true;
2152  newWidgetInfo.showLabel = widgetDef->showLabel();
2153  break;
2154  }
2155 
2156  case QgsAttributeEditorElement::AeTypeHtmlElement:
2157  {
2158  const QgsAttributeEditorHtmlElement *elementDef = static_cast<const QgsAttributeEditorHtmlElement *>( widgetDef );
2159 
2160  QgsHtmlWidgetWrapper *htmlWrapper = new QgsHtmlWidgetWrapper( mLayer, nullptr, this );
2161  context.setAttributeFormMode( mMode );
2162  htmlWrapper->setHtmlCode( elementDef->htmlCode() );
2163  htmlWrapper->reinitWidget();
2164  mWidgets.append( htmlWrapper );
2165 
2166  newWidgetInfo.widget = htmlWrapper->widget();
2167  newWidgetInfo.labelText = elementDef->name();
2168  newWidgetInfo.labelOnTop = true;
2169  newWidgetInfo.showLabel = widgetDef->showLabel();
2170  break;
2171  }
2172 
2173  default:
2174  QgsDebugMsg( QStringLiteral( "Unknown attribute editor widget type encountered..." ) );
2175  break;
2176  }
2177 
2178  newWidgetInfo.showLabel = widgetDef->showLabel();
2179 
2180  return newWidgetInfo;
2181 }
2182 
2183 void QgsAttributeForm::addWidgetWrapper( QgsEditorWidgetWrapper *eww )
2184 {
2185  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
2186  {
2187  QgsEditorWidgetWrapper *meww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2188  if ( meww )
2189  {
2190  // if another widget wrapper exists for the same field
2191  // synchronise them
2192  if ( meww->field() == eww->field() )
2193  {
2196  break;
2197  }
2198  }
2199  }
2200 
2201  mWidgets.append( eww );
2202 }
2203 
2204 void QgsAttributeForm::createWrappers()
2205 {
2206  QList<QWidget *> myWidgets = findChildren<QWidget *>();
2207  const QList<QgsField> fields = mLayer->fields().toList();
2208 
2209  const auto constMyWidgets = myWidgets;
2210  for ( QWidget *myWidget : constMyWidgets )
2211  {
2212  // Check the widget's properties for a relation definition
2213  QVariant vRel = myWidget->property( "qgisRelation" );
2214  if ( vRel.isValid() )
2215  {
2217  QgsRelation relation = relMgr->relation( vRel.toString() );
2218  if ( relation.isValid() )
2219  {
2220  QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
2221  rww->setConfig( mLayer->editFormConfig().widgetConfig( relation.id() ) );
2222  rww->setContext( mContext );
2223  rww->widget(); // Will initialize the widget
2224  mWidgets.append( rww );
2225  }
2226  }
2227  else
2228  {
2229  const auto constFields = fields;
2230  for ( const QgsField &field : constFields )
2231  {
2232  if ( field.name() == myWidget->objectName() )
2233  {
2234  int idx = mLayer->fields().lookupField( field.name() );
2235 
2236  QgsEditorWidgetWrapper *eww = QgsGui::editorWidgetRegistry()->create( mLayer, idx, myWidget, this, mContext );
2237  addWidgetWrapper( eww );
2238  }
2239  }
2240  }
2241  }
2242 }
2243 
2244 void QgsAttributeForm::afterWidgetInit()
2245 {
2246  bool isFirstEww = true;
2247 
2248  const auto constMWidgets = mWidgets;
2249  for ( QgsWidgetWrapper *ww : constMWidgets )
2250  {
2251  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2252 
2253  if ( eww )
2254  {
2255  if ( isFirstEww )
2256  {
2257  setFocusProxy( eww->widget() );
2258  isFirstEww = false;
2259  }
2260 
2261  connect( eww, &QgsEditorWidgetWrapper::valuesChanged, this, &QgsAttributeForm::onAttributeChanged );
2262  connect( eww, &QgsEditorWidgetWrapper::constraintStatusChanged, this, &QgsAttributeForm::onConstraintStatusChanged );
2263  }
2264  }
2265 }
2266 
2267 
2268 bool QgsAttributeForm::eventFilter( QObject *object, QEvent *e )
2269 {
2270  Q_UNUSED( object )
2271 
2272  if ( e->type() == QEvent::KeyPress )
2273  {
2274  QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( e );
2275  if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
2276  {
2277  // Re-emit to this form so it will be forwarded to parent
2278  event( e );
2279  return true;
2280  }
2281  }
2282 
2283  return false;
2284 }
2285 
2286 void QgsAttributeForm::scanForEqualAttributes( QgsFeatureIterator &fit,
2287  QSet< int > &mixedValueFields,
2288  QHash< int, QVariant > &fieldSharedValues ) const
2289 {
2290  mixedValueFields.clear();
2291  fieldSharedValues.clear();
2292 
2293  QgsFeature f;
2294  bool first = true;
2295  while ( fit.nextFeature( f ) )
2296  {
2297  for ( int i = 0; i < mLayer->fields().count(); ++i )
2298  {
2299  if ( mixedValueFields.contains( i ) )
2300  continue;
2301 
2302  if ( first )
2303  {
2304  fieldSharedValues[i] = f.attribute( i );
2305  }
2306  else
2307  {
2308  if ( fieldSharedValues.value( i ) != f.attribute( i ) )
2309  {
2310  fieldSharedValues.remove( i );
2311  mixedValueFields.insert( i );
2312  }
2313  }
2314  }
2315  first = false;
2316 
2317  if ( mixedValueFields.count() == mLayer->fields().count() )
2318  {
2319  // all attributes are mixed, no need to keep scanning
2320  break;
2321  }
2322  }
2323 }
2324 
2325 
2326 void QgsAttributeForm::layerSelectionChanged()
2327 {
2328  switch ( mMode )
2329  {
2336  break;
2337 
2339  resetMultiEdit( true );
2340  break;
2341  }
2342 }
2343 
2345 {
2346  mIsSettingMultiEditFeatures = true;
2347  mMultiEditFeatureIds = fids;
2348 
2349  if ( fids.isEmpty() )
2350  {
2351  // no selected features
2352  QMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
2353  for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
2354  {
2355  wIt.value()->initialize( QVariant() );
2356  }
2357  mIsSettingMultiEditFeatures = false;
2358  return;
2359  }
2360 
2361  QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFids( fids ) );
2362 
2363  // Scan through all features to determine which attributes are initially the same
2364  QSet< int > mixedValueFields;
2365  QHash< int, QVariant > fieldSharedValues;
2366  scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
2367 
2368  // also fetch just first feature
2369  fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFid( *fids.constBegin() ) );
2370  QgsFeature firstFeature;
2371  fit.nextFeature( firstFeature );
2372 
2373  const auto constMixedValueFields = mixedValueFields;
2374  for ( int fieldIndex : qgis::as_const( mixedValueFields ) )
2375  {
2376  if ( QgsAttributeFormEditorWidget *w = mFormEditorWidgets.value( fieldIndex, nullptr ) )
2377  {
2378  const QStringList additionalFields = w->editorWidget()->additionalFields();
2379  QVariantList additionalFieldValues;
2380  for ( const QString &additionalField : additionalFields )
2381  additionalFieldValues << firstFeature.attribute( additionalField );
2382  w->initialize( firstFeature.attribute( fieldIndex ), true, additionalFieldValues );
2383  }
2384  }
2385  QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
2386  for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
2387  {
2388  if ( QgsAttributeFormEditorWidget *w = mFormEditorWidgets.value( sharedValueIt.key(), nullptr ) )
2389  {
2390  bool mixed = false;
2391  const QStringList additionalFields = w->editorWidget()->additionalFields();
2392  for ( const QString &additionalField : additionalFields )
2393  {
2394  int index = mLayer->fields().indexFromName( additionalField );
2395  if ( constMixedValueFields.contains( index ) )
2396  {
2397  // if additional field are mixed, it is considered as mixed
2398  mixed = true;
2399  break;
2400  }
2401  }
2402  QVariantList additionalFieldValues;
2403  if ( mixed )
2404  {
2405  for ( const QString &additionalField : additionalFields )
2406  additionalFieldValues << firstFeature.attribute( additionalField );
2407  w->initialize( firstFeature.attribute( sharedValueIt.key() ), true, additionalFieldValues );
2408  }
2409  else
2410  {
2411  for ( const QString &additionalField : additionalFields )
2412  {
2413  int index = mLayer->fields().indexFromName( additionalField );
2414  Q_ASSERT( fieldSharedValues.contains( index ) );
2415  additionalFieldValues << fieldSharedValues.value( index );
2416  }
2417  w->initialize( sharedValueIt.value(), false, additionalFieldValues );
2418  }
2419  }
2420  }
2421  mIsSettingMultiEditFeatures = false;
2422 }
2423 
2425 {
2426  if ( mOwnsMessageBar )
2427  delete mMessageBar;
2428  mOwnsMessageBar = false;
2429  mMessageBar = messageBar;
2430 }
2431 
2433 {
2435  {
2436  Q_ASSERT( false );
2437  }
2438 
2439  QStringList filters;
2440  for ( QgsAttributeFormWidget *widget : mFormWidgets )
2441  {
2442  QString filter = widget->currentFilterExpression();
2443  if ( !filter.isNull() )
2444  filters << '(' + filter + ')';
2445  }
2446 
2447  return filters.join( QLatin1String( " AND " ) );
2448 }
2449 
2451 {
2452  mExtraContextScope.reset( extraScope );
2453 }
2454 
2455 void QgsAttributeForm::ContainerInformation::apply( QgsExpressionContext *expressionContext )
2456 {
2457  bool newVisibility = expression.evaluate( expressionContext ).toBool();
2458 
2459  if ( newVisibility != isVisible )
2460  {
2461  if ( tabWidget )
2462  {
2463  tabWidget->setTabVisible( widget, newVisibility );
2464  }
2465  else
2466  {
2467  widget->setVisible( newVisibility );
2468  }
2469 
2470  isVisible = newVisibility;
2471  }
2472 }
2473 
2474 void QgsAttributeForm::updateJoinedFields( const QgsEditorWidgetWrapper &eww )
2475 {
2476  if ( !eww.layer()->fields().exists( eww.fieldIdx() ) )
2477  return;
2478 
2479  QgsFeature formFeature;
2480  QgsField field = eww.layer()->fields().field( eww.fieldIdx() );
2481  QList<const QgsVectorLayerJoinInfo *> infos = eww.layer()->joinBuffer()->joinsWhereFieldIsId( field );
2482 
2483  if ( infos.count() == 0 || !currentFormValuesFeature( formFeature ) )
2484  return;
2485 
2486  const QString hint = tr( "No feature joined" );
2487  const auto constInfos = infos;
2488  for ( const QgsVectorLayerJoinInfo *info : constInfos )
2489  {
2490  if ( !info->isDynamicFormEnabled() )
2491  continue;
2492 
2493  QgsFeature joinFeature = mLayer->joinBuffer()->joinedFeatureOf( info, formFeature );
2494 
2495  mJoinedFeatures[info] = joinFeature;
2496 
2497  if ( info->hasSubset() )
2498  {
2499  const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( *info );
2500 
2501  const auto constSubsetNames = subsetNames;
2502  for ( const QString &field : constSubsetNames )
2503  {
2504  QString prefixedName = info->prefixedFieldName( field );
2505  QVariant val;
2506  QString hintText = hint;
2507 
2508  if ( joinFeature.isValid() )
2509  {
2510  val = joinFeature.attribute( field );
2511  hintText.clear();
2512  }
2513 
2514  changeAttribute( prefixedName, val, hintText );
2515  }
2516  }
2517  else
2518  {
2519  const QgsFields joinFields = joinFeature.fields();
2520  for ( const QgsField &field : joinFields )
2521  {
2522  QString prefixedName = info->prefixedFieldName( field );
2523  QVariant val;
2524  QString hintText = hint;
2525 
2526  if ( joinFeature.isValid() )
2527  {
2528  val = joinFeature.attribute( field.name() );
2529  hintText.clear();
2530  }
2531 
2532  changeAttribute( prefixedName, val, hintText );
2533  }
2534  }
2535  }
2536 }
2537 
2538 bool QgsAttributeForm::fieldIsEditable( int fieldIndex ) const
2539 {
2540  return QgsVectorLayerUtils::fieldIsEditable( mLayer, fieldIndex, mFeature );
2541 }
2542 
2543 void QgsAttributeForm::updateDefaultValueDependencies()
2544 {
2545  mDefaultValueDependencies.clear();
2546  //create defaultValueDependencies
2547  for ( QgsWidgetWrapper *ww : qgis::as_const( mWidgets ) )
2548  {
2549  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
2550  if ( eww )
2551  {
2553  const QSet<QString> referencedColumns = exp.referencedColumns();
2554  for ( const QString &referencedColumn : referencedColumns )
2555  {
2556  if ( referencedColumn == QgsFeatureRequest::ALL_ATTRIBUTES )
2557  {
2558  const QList<int> allAttributeIds( mLayer->fields().allAttributesList() );
2559 
2560  for ( const int id : allAttributeIds )
2561  {
2562  mDefaultValueDependencies.insertMulti( id, eww );
2563  }
2564  }
2565  else
2566  {
2567  mDefaultValueDependencies.insertMulti( mLayer->fields().lookupField( referencedColumn ), eww );
2568  }
2569  }
2570  }
2571  }
2572 }
2573 
2574 void QgsAttributeForm::updateIcon( QgsEditorWidgetWrapper *eww )
2575 {
2576  if ( !eww->widget() || !mIconMap[eww->widget()] )
2577  return;
2578 
2579  // no icon by default
2580  mIconMap[eww->widget()]->hide();
2581 
2582  if ( !eww->widget()->isEnabled() && mLayer->isEditable() )
2583  {
2584  if ( mLayer->fields().fieldOrigin( eww->fieldIdx() ) == QgsFields::OriginJoin )
2585  {
2586  int srcFieldIndex;
2587  const QgsVectorLayerJoinInfo *info = mLayer->joinBuffer()->joinForFieldIndex( eww->fieldIdx(), mLayer->fields(), srcFieldIndex );
2588 
2589  if ( !info )
2590  return;
2591 
2592  if ( !info->isEditable() )
2593  {
2594  const QString file = QStringLiteral( "/mIconJoinNotEditable.svg" );
2595  const QString tooltip = tr( "Join settings do not allow editing" );
2596  reloadIcon( file, tooltip, mIconMap[eww->widget()] );
2597  }
2598  else if ( mMode == QgsAttributeEditorContext::AddFeatureMode && !info->hasUpsertOnEdit() )
2599  {
2600  const QString file = QStringLiteral( "mIconJoinHasNotUpsertOnEdit.svg" );
2601  const QString tooltip = tr( "Join settings do not allow upsert on edit" );
2602  reloadIcon( file, tooltip, mIconMap[eww->widget()] );
2603  }
2604  else if ( !info->joinLayer()->isEditable() )
2605  {
2606  const QString file = QStringLiteral( "/mIconJoinedLayerNotEditable.svg" );
2607  const QString tooltip = tr( "Joined layer is not toggled editable" );
2608  reloadIcon( file, tooltip, mIconMap[eww->widget()] );
2609  }
2610  }
2611  }
2612 }
2613 
2614 void QgsAttributeForm::reloadIcon( const QString &file, const QString &tooltip, QSvgWidget *sw )
2615 {
2616  sw->load( QgsApplication::iconPath( file ) );
2617  sw->setToolTip( tooltip );
2618  sw->show();
2619 }
@ Warning
Definition: qgis.h:91
@ Info
Definition: qgis.h:90
@ Success
Definition: qgis.h:93
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
static QgsNetworkContentFetcherRegistry * networkContentFetcherRegistry()
Returns the application's network content registry used for fetching temporary files during QGIS sess...
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
This class contains context information for attribute editor widgets.
FormMode formMode() const
Returns the form mode.
QString attributeFormModeString() const
Returns given attributeFormMode as string.
@ Embed
A form was embedded as a widget on another form.
bool allowCustomUi() const
Returns true if the attribute editor should permit use of custom UI forms.
@ SearchMode
Form values are used for searching/filtering the layer.
@ FixAttributeMode
Fix feature mode, for modifying the feature attributes without saving. The updated feature is availab...
@ IdentifyMode
Identify the feature.
@ SingleEditMode
Single edit mode, for editing a single feature.
@ AggregateSearchMode
Form is in aggregate search mode, show each widget in this mode.
@ MultiEditMode
Multi edit mode, for editing fields of multiple features at once.
void setAttributeFormMode(const Mode &attributeFormMode)
Set attributeFormMode for the edited form.
Mode attributeFormMode() const
Returns current attributeFormMode.
A widget consisting of both an editor widget and additional widgets for controlling the behavior of t...
bool hasChanged() const
Returns true if the widget's value has been changed since it was initialized.
void changesCommitted()
Called when field values have been committed;.
QVariant currentValue() const
Returns the current value of the attached editor widget.
void setConstraintStatus(const QString &constraint, const QString &description, const QString &err, QgsEditorWidgetWrapper::ConstraintResult result)
Set the constraint status for this widget.
void createSearchWidgetWrappers(const QgsAttributeEditorContext &context=QgsAttributeEditorContext()) override
Creates the search widget wrappers for the widget used when the form is in search mode.
void setConstraintResultVisible(bool editable)
Set the constraint result label visible or invisible according to the layer editable status.
This class helps to support legacy open form scripts to be compatible with the new QgsAttributeForm s...
Widget to show for child relations on an attribute form.
void createSearchWidgetWrappers(const QgsAttributeEditorContext &context=QgsAttributeEditorContext()) override
Creates the search widget wrappers for the widget used when the form is in search mode.
Base class for all widgets shown on a QgsAttributeForm.
void resetSearch()
Resets the search/filter value of the widget.
virtual QString currentFilterExpression() const
Creates an expression matching the current search filter value and search properties represented in t...
@ SearchMode
Layer search/filter mode.
@ MultiEditMode
Multi edit mode, both the editor widget and a QgsMultiEditToolButton is shown.
@ DefaultMode
Default mode, only the editor widget is shown.
@ AggregateSearchMode
Embedded in a search form, show additional aggregate function toolbutton.
void showButtonBox()
Shows the button box (OK/Cancel) and disables auto-commit.
void parentFormValueChanged(const QString &attribute, const QVariant &newValue)
Is called in embedded forms when an attribute value in the parent form has changed to newValue.
void setFeature(const QgsFeature &feature)
Update all editors to correspond to a different feature.
void setMultiEditFeatureIds(const QgsFeatureIds &fids)
Sets all feature IDs which are to be edited if the form is in multiedit mode.
void refreshFeature()
reload current feature
void beforeSave(bool &ok)
Will be emitted before the feature is saved.
QgsVectorLayer * layer()
Returns the layer for which this form is shown.
bool eventFilter(QObject *object, QEvent *event) override
Intercepts keypress on custom form (escape should not close it)
bool saveWithDetails(QString *error=nullptr)
Save all the values from the editors to the layer.
void addInterface(QgsAttributeFormInterface *iface)
Takes ownership.
void setExtraContextScope(QgsExpressionContextScope *extraScope)
Sets an additional expression context scope to be used for calculations in this form.
void changeAttribute(const QString &field, const QVariant &value, const QString &hintText=QString())
Call this to change the content of a given attribute.
bool save()
Save all the values from the editors to the layer.
void filterExpressionSet(const QString &expression, QgsAttributeForm::FilterType type)
Emitted when a filter expression is set using the form.
void hideButtonBox()
Hides the button box (OK/Cancel) and enables auto-commit.
const QgsFeature & feature()
void closed()
Emitted when the user selects the close option from the form's button bar.
void resetSearch()
Resets the search/filter form values.
void widgetValueChanged(const QString &attribute, const QVariant &value, bool attributeChanged)
Notifies about changes of attributes.
QString aggregateFilter() const
The aggregate filter is only useful if the form is in AggregateFilter mode.
void flashFeatures(const QString &filter)
Emitted when the user chooses to flash a filtered set of features.
void resetValues()
Sets all values to the values of the current feature.
~QgsAttributeForm() override
QgsAttributeForm(QgsVectorLayer *vl, const QgsFeature &feature=QgsFeature(), const QgsAttributeEditorContext &context=QgsAttributeEditorContext(), QWidget *parent=nullptr)
void disconnectButtonBox()
Disconnects the button box (OK/Cancel) from the accept/resetValues slots If this method is called,...
bool editable()
Returns if the form is currently in editable mode.
void setMessageBar(QgsMessageBar *messageBar)
Sets the message bar to display feedback from the form in.
void modeChanged(QgsAttributeEditorContext::Mode mode)
Emitted when the form changes mode.
void featureSaved(const QgsFeature &feature)
Emitted when a feature is changed or added.
void displayWarning(const QString &message)
Displays a warning message in the form message bar.
void zoomToFeatures(const QString &filter)
Emitted when the user chooses to zoom to a filtered set of features.
Q_DECL_DEPRECATED void attributeChanged(const QString &attribute, const QVariant &value)
Notifies about changes of attributes, this signal is not emitted when the value is set back to the or...
void setMode(QgsAttributeEditorContext::Mode mode)
Sets the current mode of the form.
@ ReplaceFilter
Filter should replace any existing filter.
@ FilterOr
Filter should be combined using "OR".
@ FilterAnd
Filter should be combined using "AND".
QgsAttributeEditorContext::Mode mode() const
Returns the current mode of the form.
A vector of attributes.
Definition: qgsattributes.h:58
Q_GADGET QString expression
QgsEditorWidgetSetup findBest(const QgsVectorLayer *vl, const QString &fieldName) const
Find the best editor widget and its configuration for a given field.
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.
Holder for the widget type and its configuration for a field.
QVariantMap config() const
Manages an editor widget Widget and wrapper share the same parent.
virtual QVariant value() const =0
Will be used to access the widget's value.
virtual QVariantList additionalFieldValues() const
Will be used to access the widget's values for potential additional fields handled by the widget.
int fieldIdx() const
Access the field index.
virtual void parentFormValueChanged(const QString &attribute, const QVariant &value)
Is called in embedded form widgets when an attribute value in the parent form has changed.
virtual QStringList additionalFields() const
Returns the list of additional fields which the editor handles.
QString constraintFailureReason() const
Returns the reason why a constraint check has failed (or an empty string if constraint check was succ...
void valuesChanged(const QVariant &value, const QVariantList &additionalFieldValues=QVariantList())
Emit this signal, whenever the value changed.
bool isValidConstraint() const
Gets the current constraint status.
void updateConstraint(const QgsFeature &featureContext, QgsFieldConstraints::ConstraintOrigin constraintOrigin=QgsFieldConstraints::ConstraintOriginNotSet)
Update constraint.
void setValues(const QVariant &value, const QVariantList &additionalValues)
Is called when the value of the widget or additional field values needs to be changed.
virtual void setHint(const QString &hintText)
Add a hint text on the widget.
QgsField field() const
Access the field.
ConstraintResult
Result of constraint checks.
void constraintStatusChanged(const QString &constraint, const QString &desc, const QString &err, QgsEditorWidgetWrapper::ConstraintResult status)
Emit this signal when the constraint status changed.
virtual void setValue(const QVariant &value)
Is called when the value of the widget needs to be changed.
bool isBlockingCommit() const
Returns true if the widget is preventing the feature from being committed.
void setConstraintResultVisible(bool constraintResultVisible)
Sets whether the constraint result is visible.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * formScope(const QgsFeature &formFeature=QgsFeature(), const QString &formMode=QString())
Creates a new scope which contains functions and variables from the current attribute form/table form...
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
static const QString ALL_ATTRIBUTES
A special attribute that if set matches all attributes.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:56
bool setAttribute(int field, const QVariant &attr)
Set an attribute's value by field index.
Definition: qgsfeature.cpp:236
QgsAttributes attributes
Definition: qgsfeature.h:65
QgsFields fields
Definition: qgsfeature.h:66
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:134
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:169
void setValid(bool validity)
Sets the validity of the feature.
Definition: qgsfeature.cpp:195
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:190
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:287
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
ConstraintOrigin
Origin of constraints.
@ ConstraintOriginNotSet
Constraint is not set.
@ ConstraintOriginLayer
Constraint was set by layer.
QString constraintExpression() const
Returns the constraint expression for the field, if set.
static QString fieldToolTipExtended(const QgsField &field, const QgsVectorLayer *layer)
Returns a HTML formatted tooltip string for a field, containing details like the field name,...
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString name
Definition: qgsfield.h:60
QString displayName() const
Returns the name to use when displaying this field.
Definition: qgsfield.cpp:88
QVariant::Type type
Definition: qgsfield.h:58
QgsDefaultValue defaultValueDefinition
Definition: qgsfield.h:62
QString comment
Definition: qgsfield.h:59
QgsFieldConstraints constraints
Definition: qgsfield.h:63
Container of fields for a vector layer.
Definition: qgsfields.h:45
QList< QgsField > toList() const
Utility function to return a list of QgsField instances.
Definition: qgsfields.cpp:212
QgsAttributeList allAttributesList() const
Utility function to get list of attribute indexes.
Definition: qgsfields.cpp:371
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
@ OriginJoin
Field comes from a joined layer (originIndex / 1000 = index of the join, originIndex % 1000 = index w...
Definition: qgsfields.h:52
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
FieldOrigin fieldOrigin(int fieldIdx) const
Gets field's origin (value from an enumeration)
Definition: qgsfields.cpp:189
QgsField field(int fieldIdx) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:168
bool exists(int i) const
Returns if a field index is valid.
Definition: qgsfields.cpp:153
int size() const
Returns number of items.
Definition: qgsfields.cpp:138
QgsField at(int i) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:344
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
Definition: qgsgui.cpp:76
static bool pythonMacroAllowed(void(*lambda)()=nullptr, QgsMessageBar *messageBar=nullptr)
Returns true if python macros are currently allowed to be run If the global option is to ask user,...
Definition: qgsgui.cpp:281
Wraps a QQuickWidget to display HTML code.
void reinitWidget()
Clears the content and makes new initialization.
void setHtmlCode(const QString &htmlCode)
Sets the HTML code to htmlCode.
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:122
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
Represents an item shown within a QgsMessageBar widget.
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:61
void pushMessage(const QString &text, Qgis::MessageLevel level=Qgis::Info, int duration=-1)
A convenience method for pushing a message with the specified text to the bar.
bool popWidget(QgsMessageBarItem *item)
Remove the specified item from the bar, and display the next most recent one in the stack.
void pushItem(QgsMessageBarItem *item)
Display a message item on the bar, after hiding the currently visible one and putting it in a stack.
QFile * localFile(const QString &filePathOrUrl)
Returns a QFile from a local file or to a temporary file previously fetched by the registry.
QgsRelationManager * relationManager
Definition: qgsproject.h:109
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:501
A store for object properties.
Definition: qgsproperty.h:232
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
static bool eval(const QString &command, QString &result)
Eval a Python statement.
Wraps a QQuickWidget to display QML code.
void setQmlCode(const QString &qmlCode)
writes the qmlCode into a temporary file
This class manages a set of relations between layers.
QList< QgsRelation > referencedRelations(const QgsVectorLayer *layer=nullptr) const
Gets all relations where this layer is the referenced part (i.e.
Q_INVOKABLE QgsRelation relation(const QString &id) const
Gets access to a relation by its id.
void setShowLabel(bool showLabel)
Defines if a title label should be shown for this widget.
void setLabel(const QString &label=QString())
Sets label for this element If it's empty it takes the relation id as label.
void setWidgetConfig(const QVariantMap &config)
Will set the config of this widget wrapper to the specified config.
void setForceSuppressFormPopup(bool forceSuppressFormPopup)
Sets force suppress form popup status to forceSuppressFormPopup for this widget and for the vectorLay...
void setNmRelationId(const QVariant &nmRelationId=QVariant())
Sets nmRelationId for the relation id of the second relation involved in an N:M relation.
Q_GADGET QString id
Definition: qgsrelation.h:46
bool isValid
Definition: qgsrelation.h:50
A QScrollArea subclass with improved scrolling behavior.
Definition: qgsscrollarea.h:42
The QgsTabWidget class is the same as the QTabWidget but with additional methods to temporarily hide/...
Definition: qgstabwidget.h:30
void setTabVisible(QWidget *tab, bool visible)
Control the visibility for the tab with the given widget.
Wraps a text widget.
bool isInvalidJSON()
Returns whether the text edit widget contains Invalid JSON.
const QgsVectorLayerJoinInfo * joinForFieldIndex(int index, const QgsFields &fields, int &sourceFieldIndex) const
Finds the vector join for a layer field index.
QList< const QgsVectorLayerJoinInfo * > joinsWhereFieldIsId(const QgsField &field) const
Returns joins where the field of a target layer is considered as an id.
QgsFeature joinedFeatureOf(const QgsVectorLayerJoinInfo *info, const QgsFeature &feature) const
Returns the joined feature corresponding to the feature.
Defines left outer join from our vector layer to some other vector layer.
bool isDynamicFormEnabled() const
Returns whether the form has to be dynamically updated with joined fields when a feature is being cre...
bool hasUpsertOnEdit() const
Returns whether a feature created on the target layer has to impact the joined layer by creating a ne...
bool isEditable() const
Returns whether joined fields may be edited through the form of the target layer.
QgsVectorLayer * joinLayer() const
Returns joined layer (may be nullptr if the reference was set by layer ID and not resolved yet)
QStringList * joinFieldNamesSubset() const
Returns the subset of fields to be used from joined layer.
static bool fieldIsEditable(const QgsVectorLayer *layer, int fieldIndex, const QgsFeature &feature)
Tests whether a field is editable for a particular feature.
Represents a vector layer which manages a vector based data sets.
QString attributeDisplayName(int index) const
Convenience function that returns the attribute alias if defined or the field name else.
void beforeRemovingExpressionField(int idx)
Will be emitted, when an expression field is going to be deleted from this vector layer.
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
void editingStarted()
Emitted when editing on this layer has started.
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
void endEditCommand()
Finish edit command and add it to undo/redo stack.
void destroyEditCommand()
Destroy active command and reverts all changes in it.
QgsVectorLayerJoinBuffer * joinBuffer()
Returns the join buffer object.
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
Q_INVOKABLE void selectByExpression(const QString &expression, QgsVectorLayer::SelectBehavior behavior=QgsVectorLayer::SetSelection)
Selects matching features using an expression.
bool changeAttributeValues(QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues=QgsAttributeMap(), bool skipDefaultValues=false)
Changes attributes' values for a feature (but does not immediately commit the changes).
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
SelectBehavior
Selection behavior.
@ RemoveFromSelection
Remove from current selection.
@ IntersectSelection
Modify current selection to include only select features which match.
@ AddToSelection
Add selection to current selection.
@ SetSelection
Set selection, removing any existing selection.
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).
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a single feature to the sink.
void selectionChanged(const QgsFeatureIds &selected, const QgsFeatureIds &deselected, bool clearAndSelect)
Emitted when selection was changed.
void beforeAddingExpressionField(const QString &fieldName)
Will be emitted, when an expression field is going to be added to this vector layer.
void updatedFields()
Emitted whenever the fields available from this layer have been changed.
QVariant defaultValue(int index, const QgsFeature &feature=QgsFeature(), QgsExpressionContext *context=nullptr) const
Returns the calculated default value for the specified field index.
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
QgsEditFormConfig editFormConfig
void beforeModifiedCheck() const
Emitted when the layer is checked for modifications. Use for last-minute additions.
Manages an editor widget Widget and wrapper share the same parent.
QWidget * widget()
Access the widget managed by this wrapper.
void setConfig(const QVariantMap &config)
Will set the config of this wrapper to the specified config.
QgsVectorLayer * layer() const
Returns the vector layer associated with the widget.
void setContext(const QgsAttributeEditorContext &context)
Set the context in which this widget is shown.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsVariantEqual(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether they are equal, two NULL values are always treated a...
Definition: qgis.cpp:265
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:798
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:797
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:38
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
const QgsField & field
Definition: qgsfield.h:472
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38