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