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