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