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