QGIS API Documentation  2.99.0-Master (314842d)
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 
36 #include <QDir>
37 #include <QTextStream>
38 #include <QFileInfo>
39 #include <QFile>
40 #include <QFormLayout>
41 #include <QGridLayout>
42 #include <QGroupBox>
43 #include <QKeyEvent>
44 #include <QLabel>
45 #include <QPushButton>
46 #include <QUiLoader>
47 #include <QMessageBox>
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!! (e.g., 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::searchZoomTo()
409 {
410  QString filter = createFilterExpression();
411  if ( filter.isEmpty() )
412  return;
413 
414  emit zoomToFeatures( filter );
415 }
416 
417 void QgsAttributeForm::filterAndTriggered()
418 {
419  QString filter = createFilterExpression();
420  if ( filter.isEmpty() )
421  return;
422 
423  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
425  emit filterExpressionSet( filter, FilterAnd );
426 }
427 
428 void QgsAttributeForm::filterOrTriggered()
429 {
430  QString filter = createFilterExpression();
431  if ( filter.isEmpty() )
432  return;
433 
434  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
436  emit filterExpressionSet( filter, FilterOr );
437 }
438 
439 void QgsAttributeForm::pushSelectedFeaturesMessage()
440 {
441  int count = mLayer->selectedFeatureCount();
442  if ( count > 0 )
443  {
444  mMessageBar->pushMessage( QString(),
445  tr( "%1 matching %2 selected" ).arg( count )
446  .arg( count == 1 ? tr( "feature" ) : tr( "features" ) ),
448  messageTimeout() );
449  }
450  else
451  {
452  mMessageBar->pushMessage( QString(),
453  tr( "No matching features found" ),
455  messageTimeout() );
456  }
457 }
458 
459 void QgsAttributeForm::runSearchSelect( QgsVectorLayer::SelectBehavior behavior )
460 {
461  QString filter = createFilterExpression();
462  if ( filter.isEmpty() )
463  return;
464 
465  mLayer->selectByExpression( filter, behavior );
466  pushSelectedFeaturesMessage();
467  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
469 }
470 
471 void QgsAttributeForm::searchSetSelection()
472 {
473  runSearchSelect( QgsVectorLayer::SetSelection );
474 }
475 
476 void QgsAttributeForm::searchAddToSelection()
477 {
478  runSearchSelect( QgsVectorLayer::AddToSelection );
479 }
480 
481 void QgsAttributeForm::searchRemoveFromSelection()
482 {
483  runSearchSelect( QgsVectorLayer::RemoveFromSelection );
484 }
485 
486 void QgsAttributeForm::searchIntersectSelection()
487 {
488  runSearchSelect( QgsVectorLayer::IntersectSelection );
489 }
490 
491 bool QgsAttributeForm::saveMultiEdits()
492 {
493  //find changed attributes
494  QgsAttributeMap newAttributeValues;
495  QMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
496  for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
497  {
498  QgsAttributeFormEditorWidget *w = wIt.value();
499  if ( !w->hasChanged() )
500  continue;
501 
502  if ( !w->currentValue().isValid() // if the widget returns invalid (== do not change)
503  || mLayer->editFormConfig().readOnly( wIt.key() ) ) // or the field cannot be edited ...
504  {
505  continue;
506  }
507 
508  // let editor know we've accepted the changes
509  w->changesCommitted();
510 
511  newAttributeValues.insert( wIt.key(), w->currentValue() );
512  }
513 
514  if ( newAttributeValues.isEmpty() )
515  {
516  //nothing to change
517  return true;
518  }
519 
520 #if 0
521  // prompt for save
522  int res = QMessageBox::information( this, tr( "Multiedit attributes" ),
523  tr( "Edits will be applied to all selected features" ), QMessageBox::Ok | QMessageBox::Cancel );
524  if ( res != QMessageBox::Ok )
525  {
526  resetMultiEdit();
527  return false;
528  }
529 #endif
530 
531  mLayer->beginEditCommand( tr( "Updated multiple feature attributes" ) );
532 
533  bool success = true;
534 
535  Q_FOREACH ( QgsFeatureId fid, mMultiEditFeatureIds )
536  {
537  QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
538  for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
539  {
540  success &= mLayer->changeAttributeValue( fid, aIt.key(), aIt.value() );
541  }
542  }
543 
544  clearMultiEditMessages();
545  if ( success )
546  {
547  mLayer->endEditCommand();
548  mLayer->triggerRepaint();
549  mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Attribute changes for multiple features applied" ), QgsMessageBar::SUCCESS, messageTimeout() );
550  }
551  else
552  {
553  mLayer->destroyEditCommand();
554  mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Changes could not be applied" ), QgsMessageBar::WARNING, messageTimeout() );
555  }
556 
557  if ( !mButtonBox->isVisible() )
558  mMessageBar->pushItem( mMultiEditMessageBarItem );
559  return success;
560 }
561 
563 {
564  if ( mIsSaving )
565  return true;
566 
567  mIsSaving = true;
568 
569  bool success = true;
570 
571  emit beforeSave( success );
572 
573  // Somebody wants to prevent this form from saving
574  if ( !success )
575  return false;
576 
577  switch ( mMode )
578  {
579  case SingleEditMode:
580  case AddFeatureMode:
581  case SearchMode:
582  success = saveEdits();
583  break;
584 
585  case MultiEditMode:
586  success = saveMultiEdits();
587  break;
588  }
589 
590  mIsSaving = false;
591  mUnsavedMultiEditChanges = false;
592 
593  return success;
594 }
595 
597 {
598  Q_FOREACH ( QgsWidgetWrapper *ww, mWidgets )
599  {
600  ww->setFeature( mFeature );
601  }
602 }
603 
605 {
606  Q_FOREACH ( QgsAttributeFormEditorWidget *w, findChildren< QgsAttributeFormEditorWidget * >() )
607  {
608  w->resetSearch();
609  }
610 }
611 
612 void QgsAttributeForm::clearMultiEditMessages()
613 {
614  if ( mMultiEditUnsavedMessageBarItem )
615  {
616  if ( !mButtonBox->isVisible() )
617  mMessageBar->popWidget( mMultiEditUnsavedMessageBarItem );
618  mMultiEditUnsavedMessageBarItem = nullptr;
619  }
620  if ( mMultiEditMessageBarItem )
621  {
622  if ( !mButtonBox->isVisible() )
623  mMessageBar->popWidget( mMultiEditMessageBarItem );
624  mMultiEditMessageBarItem = nullptr;
625  }
626 }
627 
628 QString QgsAttributeForm::createFilterExpression() const
629 {
630  QStringList filters;
631  Q_FOREACH ( QgsAttributeFormEditorWidget *w, findChildren< QgsAttributeFormEditorWidget * >() )
632  {
633  QString filter = w->currentFilterExpression();
634  if ( !filter.isEmpty() )
635  filters << filter;
636  }
637 
638  if ( filters.isEmpty() )
639  return QString();
640 
641  QString filter = filters.join( QStringLiteral( ") AND (" ) ).prepend( '(' ).append( ')' );
642  return filter;
643 }
644 
645 void QgsAttributeForm::onAttributeChanged( const QVariant &value )
646 {
647  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
648 
649  Q_ASSERT( eww );
650 
651  switch ( mMode )
652  {
653  case SingleEditMode:
654  case AddFeatureMode:
655  {
656  // don't emit signal if it was triggered by a feature change
657  if ( !mIsSettingFeature )
658  {
659  emit attributeChanged( eww->field().name(), value );
660  }
661  break;
662  }
663  case MultiEditMode:
664  {
665  if ( !mIsSettingMultiEditFeatures )
666  {
667  mUnsavedMultiEditChanges = true;
668 
669  QLabel *msgLabel = new QLabel( tr( "Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
670  msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
671  msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
672  connect( msgLabel, &QLabel::linkActivated, this, &QgsAttributeForm::multiEditMessageClicked );
673  clearMultiEditMessages();
674 
675  mMultiEditUnsavedMessageBarItem = new QgsMessageBarItem( msgLabel, QgsMessageBar::WARNING );
676  if ( !mButtonBox->isVisible() )
677  mMessageBar->pushItem( mMultiEditUnsavedMessageBarItem );
678  }
679  break;
680  }
681  case SearchMode:
682  //nothing to do
683  break;
684  }
685 
686  updateConstraints( eww );
687 
688  // emit
689  emit attributeChanged( eww->field().name(), value );
690 }
691 
692 void QgsAttributeForm::updateAllConstraints()
693 {
694  Q_FOREACH ( QgsWidgetWrapper *ww, mWidgets )
695  {
696  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
697  if ( eww )
698  updateConstraints( eww );
699  }
700 }
701 
702 void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww )
703 {
704  // get the current feature set in the form
705  QgsFeature ft;
706  if ( currentFormFeature( ft ) )
707  {
708  // if the layer is NOT being edited then we only check layer based constraints, and not
709  // any constraints enforced by the provider. Because:
710  // 1. we want to keep browsing features nice and responsive. It's nice to give feedback as to whether
711  // the value checks out, but not if it's too slow to do so. Some constraints (e.g., unique) can be
712  // expensive to test. A user can freely remove a layer-based constraint if it proves to be too slow
713  // to test, but they are unlikely to have any control over provider-side constraints
714  // 2. the provider has already accepted the value, so presumably it doesn't violate the constraint
715  // and there's no point rechecking!
718 
719  // update eww constraint
720  eww->updateConstraint( ft, constraintOrigin );
721 
722  // update eww dependencies constraint
723  QList<QgsEditorWidgetWrapper *> deps = constraintDependencies( eww );
724 
725  Q_FOREACH ( QgsEditorWidgetWrapper *depsEww, deps )
726  depsEww->updateConstraint( ft, constraintOrigin );
727 
728  // sync ok button status
729  synchronizeEnabledState();
730 
731  mExpressionContext.setFeature( ft );
732 
733  // Recheck visibility for all containers which are controlled by this value
734  Q_FOREACH ( ContainerInformation *info, mContainerInformationDependency.value( eww->field().name() ) )
735  {
736  info->apply( &mExpressionContext );
737  }
738  }
739 }
740 
741 bool QgsAttributeForm::currentFormFeature( QgsFeature &feature )
742 {
743  bool rc = true;
744  feature = QgsFeature( mFeature );
745  QgsAttributes src = feature.attributes();
746  QgsAttributes dst = feature.attributes();
747 
748  Q_FOREACH ( QgsWidgetWrapper *ww, mWidgets )
749  {
750  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
751 
752  if ( !eww )
753  continue;
754 
755  if ( dst.count() > eww->fieldIdx() )
756  {
757  QVariant dstVar = dst.at( eww->fieldIdx() );
758  QVariant srcVar = eww->value();
759  // need to check dstVar.isNull() != srcVar.isNull()
760  // otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
761  if ( ( dstVar != srcVar || dstVar.isNull() != srcVar.isNull() ) && srcVar.isValid() && !mLayer->editFormConfig().readOnly( eww->fieldIdx() ) )
762  dst[eww->fieldIdx()] = srcVar;
763  }
764  else
765  {
766  rc = false;
767  break;
768  }
769  }
770 
771  feature.setAttributes( dst );
772 
773  return rc;
774 }
775 
776 void QgsAttributeForm::clearInvalidConstraintsMessage()
777 {
778  mTopMessageWidget->hide();
779  mInvalidConstraintMessage->clear();
780 }
781 
782 void QgsAttributeForm::displayInvalidConstraintMessage( const QStringList &f,
783  const QStringList &d )
784 {
785  clearInvalidConstraintsMessage();
786 
787  // show only the third first errors (to avoid a too long label)
788  int max = 3;
789  int size = f.size() > max ? max : f.size();
790  QString descriptions;
791  for ( int i = 0; i < size; i++ )
792  descriptions += QStringLiteral( "<li>%1: <i>%2</i></li>" ).arg( f[i], d[i] );
793 
794  QString msg = QStringLiteral( "<b>%1</b><ul>%2</ul>" ).arg( tr( "Invalid fields" ), descriptions ) ;
795 
796  mInvalidConstraintMessage->setText( msg );
797  mTopMessageWidget->show();
798 }
799 
800 void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation *info )
801 {
802  mContainerVisibilityInformation.append( info );
803  Q_FOREACH ( const QString &col, info->expression.referencedColumns() )
804  {
805  mContainerInformationDependency[ col ].append( info );
806  }
807 }
808 
809 bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields,
810  QStringList &descriptions )
811 {
812  bool valid( true );
813 
814  Q_FOREACH ( QgsWidgetWrapper *ww, mWidgets )
815  {
816  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
817  if ( eww )
818  {
819  if ( ! eww->isValidConstraint() )
820  {
821  invalidFields.append( eww->field().displayName() );
822 
823  descriptions.append( eww->constraintFailureReason() );
824 
825  if ( eww->isBlockingCommit() )
826  valid = false; // continue to get all invalid fields
827  }
828  }
829  }
830 
831  return valid;
832 }
833 
834 void QgsAttributeForm::onAttributeAdded( int idx )
835 {
836  mPreventFeatureRefresh = false;
837  if ( mFeature.isValid() )
838  {
839  QgsAttributes attrs = mFeature.attributes();
840  attrs.insert( idx, QVariant( layer()->fields().at( idx ).type() ) );
841  mFeature.setFields( layer()->fields() );
842  mFeature.setAttributes( attrs );
843  }
844  init();
845  setFeature( mFeature );
846 }
847 
848 void QgsAttributeForm::onAttributeDeleted( int idx )
849 {
850  mPreventFeatureRefresh = false;
851  if ( mFeature.isValid() )
852  {
853  QgsAttributes attrs = mFeature.attributes();
854  attrs.remove( idx );
855  mFeature.setFields( layer()->fields() );
856  mFeature.setAttributes( attrs );
857  }
858  init();
859  setFeature( mFeature );
860 }
861 
862 void QgsAttributeForm::onUpdatedFields()
863 {
864  mPreventFeatureRefresh = false;
865  if ( mFeature.isValid() )
866  {
867  QgsAttributes attrs( layer()->fields().size() );
868  for ( int i = 0; i < layer()->fields().size(); i++ )
869  {
870  int idx = mFeature.fields().indexFromName( layer()->fields().at( i ).name() );
871  if ( idx != -1 )
872  {
873  attrs[i] = mFeature.attributes().at( idx );
874  if ( mFeature.attributes().at( idx ).type() != layer()->fields().at( i ).type() )
875  {
876  attrs[i].convert( layer()->fields().at( i ).type() );
877  }
878  }
879  else
880  {
881  attrs[i] = QVariant( layer()->fields().at( i ).type() );
882  }
883  }
884  mFeature.setFields( layer()->fields() );
885  mFeature.setAttributes( attrs );
886  }
887  init();
888  setFeature( mFeature );
889 }
890 
891 void QgsAttributeForm::onConstraintStatusChanged( const QString &constraint,
892  const QString &description, const QString &err, QgsEditorWidgetWrapper::ConstraintResult result )
893 {
894  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( sender() );
895  Q_ASSERT( eww );
896 
897  QLabel *buddy = mBuddyMap.value( eww->widget() );
898 
899  if ( buddy )
900  {
901  QString tooltip = QStringLiteral( "<b>" ) + tr( "Constraints: " ) + QStringLiteral( "</b>" ) + description +
902  QStringLiteral( "<br /><b>" ) + tr( "Raw expression: " ) + QStringLiteral( "</b>" ) + constraint +
903  QStringLiteral( "<br /><b>" ) + tr( "Result: " ) + QStringLiteral( "</b>" ) + err;
904  buddy->setToolTip( tooltip );
905 
906  if ( !buddy->property( "originalText" ).isValid() )
907  buddy->setProperty( "originalText", buddy->text() );
908 
909  QString text = buddy->property( "originalText" ).toString();
910 
911  switch ( result )
912  {
914  buddy->setText( QStringLiteral( "%1<font color=\"red\">✘</font>" ).arg( text ) );
915  break;
916 
918  buddy->setText( QStringLiteral( "%1<font color=\"orange\">✘</font>" ).arg( text ) );
919  break;
920 
922  buddy->setText( QStringLiteral( "%1<font color=\"green\">✔</font>" ).arg( text ) );
923  break;
924  }
925  }
926 }
927 
928 QList<QgsEditorWidgetWrapper *> QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper *w )
929 {
930  QList<QgsEditorWidgetWrapper *> wDeps;
931  QString name = w->field().name();
932 
933  // for each widget in the current form
934  Q_FOREACH ( QgsWidgetWrapper *ww, mWidgets )
935  {
936  // get the wrapper
937  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
938  if ( eww )
939  {
940  // compare name to not compare w to itself
941  QString ewwName = eww->field().name();
942  if ( name != ewwName )
943  {
944  // get expression and referencedColumns
945  QgsExpression expr = eww->layer()->fields().at( eww->fieldIdx() ).constraints().constraintExpression();
946 
947  Q_FOREACH ( const QString &colName, expr.referencedColumns() )
948  {
949  if ( name == colName )
950  {
951  wDeps.append( eww );
952  break;
953  }
954  }
955  }
956  }
957  }
958 
959  return wDeps;
960 }
961 
962 void QgsAttributeForm::preventFeatureRefresh()
963 {
964  mPreventFeatureRefresh = true;
965 }
966 
968 {
969  if ( mPreventFeatureRefresh || mLayer->isEditable() || !mFeature.isValid() )
970  return;
971 
972  // reload feature if layer changed although not editable
973  // (datasource probably changed bypassing QgsVectorLayer)
974  if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeature.id() ) ).nextFeature( mFeature ) )
975  return;
976 
977  init();
978  setFeature( mFeature );
979 }
980 
981 void QgsAttributeForm::synchronizeEnabledState()
982 {
983  bool isEditable = ( mFeature.isValid()
984  || mMode == AddFeatureMode
985  || mMode == MultiEditMode ) && mLayer->isEditable();
986 
987  Q_FOREACH ( QgsWidgetWrapper *ww, mWidgets )
988  {
989  bool fieldEditable = true;
990  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
991  if ( eww )
992  {
993  fieldEditable = !mLayer->editFormConfig().readOnly( eww->fieldIdx() ) &&
995  FID_IS_NEW( mFeature.id() ) );
996  }
997  ww->setEnabled( isEditable && fieldEditable );
998  }
999 
1000  // push a message and disable the OK button if constraints are invalid
1001  clearInvalidConstraintsMessage();
1002 
1003  if ( mMode != SearchMode )
1004  {
1005  QStringList invalidFields, descriptions;
1006  bool validConstraint = currentFormValidConstraints( invalidFields, descriptions );
1007 
1008  if ( ! invalidFields.isEmpty() )
1009  displayInvalidConstraintMessage( invalidFields, descriptions );
1010 
1011  isEditable = isEditable & validConstraint;
1012  }
1013 
1014  // change ok button status
1015  QPushButton *okButton = mButtonBox->button( QDialogButtonBox::Ok );
1016  if ( okButton )
1017  okButton->setEnabled( isEditable );
1018 }
1019 
1020 void QgsAttributeForm::init()
1021 {
1022  QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1023 
1024  // Cleanup of any previously shown widget, we start from scratch
1025  QWidget *formWidget = nullptr;
1026 
1027  bool buttonBoxVisible = true;
1028  // Cleanup button box but preserve visibility
1029  if ( mButtonBox )
1030  {
1031  buttonBoxVisible = mButtonBox->isVisible();
1032  delete mButtonBox;
1033  mButtonBox = nullptr;
1034  }
1035 
1036  if ( mSearchButtonBox )
1037  {
1038  delete mSearchButtonBox;
1039  mSearchButtonBox = nullptr;
1040  }
1041 
1042  qDeleteAll( mWidgets );
1043  mWidgets.clear();
1044 
1045  while ( QWidget *w = this->findChild<QWidget *>() )
1046  {
1047  delete w;
1048  }
1049  delete layout();
1050 
1051  QVBoxLayout *vl = new QVBoxLayout();
1052  vl->setMargin( 0 );
1053  vl->setContentsMargins( 0, 0, 0, 0 );
1054  mMessageBar = new QgsMessageBar( this );
1055  mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1056  vl->addWidget( mMessageBar );
1057 
1058  mTopMessageWidget = new QWidget();
1059  mTopMessageWidget->hide();
1060  mTopMessageWidget->setLayout( new QHBoxLayout() );
1061 
1062  QSvgWidget *warningIcon = new QSvgWidget( QgsApplication::iconPath( QStringLiteral( "/mIconWarning.svg" ) ) );
1063  warningIcon->setFixedSize( 48, 48 );
1064  mTopMessageWidget->layout()->addWidget( warningIcon );
1065  mInvalidConstraintMessage = new QLabel( this );
1066  mTopMessageWidget->layout()->addWidget( mInvalidConstraintMessage );
1067  mTopMessageWidget->hide();
1068 
1069  vl->addWidget( mTopMessageWidget );
1070 
1071  setLayout( vl );
1072 
1073  // Get a layout
1074  QGridLayout *layout = new QGridLayout();
1075  QWidget *container = new QWidget();
1076  container->setLayout( layout );
1077  vl->addWidget( container );
1078 
1079  mFormEditorWidgets.clear();
1080 
1081  // a bar to warn the user with non-blocking messages
1082  setContentsMargins( 0, 0, 0, 0 );
1083 
1084  // Try to load Ui-File for layout
1085  if ( mContext.allowCustomUi() && mLayer->editFormConfig().layout() == QgsEditFormConfig::UiFileLayout &&
1086  !mLayer->editFormConfig().uiForm().isEmpty() )
1087  {
1088  QFile file( mLayer->editFormConfig().uiForm() );
1089 
1090  if ( file.open( QFile::ReadOnly ) )
1091  {
1092  QUiLoader loader;
1093 
1094  QFileInfo fi( mLayer->editFormConfig().uiForm() );
1095  loader.setWorkingDirectory( fi.dir() );
1096  formWidget = loader.load( &file, this );
1097  formWidget->setWindowFlags( Qt::Widget );
1098  layout->addWidget( formWidget );
1099  formWidget->show();
1100  file.close();
1101  mButtonBox = findChild<QDialogButtonBox *>();
1102  createWrappers();
1103 
1104  formWidget->installEventFilter( this );
1105  }
1106  }
1107 
1108  QgsTabWidget *tabWidget = nullptr;
1109 
1110  // Tab layout
1111  if ( !formWidget && mLayer->editFormConfig().layout() == QgsEditFormConfig::TabLayout )
1112  {
1113  int row = 0;
1114  int column = 0;
1115  int columnCount = 1;
1116 
1117  Q_FOREACH ( QgsAttributeEditorElement *widgDef, mLayer->editFormConfig().tabs() )
1118  {
1120  {
1121  QgsAttributeEditorContainer *containerDef = dynamic_cast<QgsAttributeEditorContainer *>( widgDef );
1122  if ( !containerDef )
1123  continue;
1124 
1125  if ( containerDef->isGroupBox() )
1126  {
1127  tabWidget = nullptr;
1128  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1129  layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1130  registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().data() ) );
1131  column += 2;
1132  }
1133  else
1134  {
1135  if ( !tabWidget )
1136  {
1137  tabWidget = new QgsTabWidget();
1138  layout->addWidget( tabWidget, row, column, 1, 2 );
1139  column += 2;
1140  }
1141 
1142  QWidget *tabPage = new QWidget( tabWidget );
1143 
1144  tabWidget->addTab( tabPage, widgDef->name() );
1145 
1146  if ( containerDef->visibilityExpression().enabled() )
1147  {
1148  registerContainerInformation( new ContainerInformation( tabWidget, tabPage, containerDef->visibilityExpression().data() ) );
1149  }
1150  QGridLayout *tabPageLayout = new QGridLayout();
1151  tabPage->setLayout( tabPageLayout );
1152 
1153  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1154  tabPageLayout->addWidget( widgetInfo.widget );
1155  }
1156  }
1157  else
1158  {
1159  tabWidget = nullptr;
1160  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1161  QLabel *label = new QLabel( widgetInfo.labelText );
1162  if ( columnCount > 1 && !widgetInfo.labelOnTop )
1163  {
1164  label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1165  }
1166 
1167  label->setBuddy( widgetInfo.widget );
1168 
1169  if ( !widgetInfo.showLabel )
1170  {
1171  QVBoxLayout *c = new QVBoxLayout();
1172  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1173  c->addWidget( widgetInfo.widget );
1174  layout->addLayout( c, row, column, 1, 2 );
1175  column += 2;
1176  }
1177  else if ( widgetInfo.labelOnTop )
1178  {
1179  QVBoxLayout *c = new QVBoxLayout();
1180  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1181  c->addWidget( label );
1182  c->addWidget( widgetInfo.widget );
1183  layout->addLayout( c, row, column, 1, 2 );
1184  column += 2;
1185  }
1186  else
1187  {
1188  layout->addWidget( label, row, column++ );
1189  layout->addWidget( widgetInfo.widget, row, column++ );
1190  }
1191  }
1192 
1193  if ( column >= columnCount * 2 )
1194  {
1195  column = 0;
1196  row += 1;
1197  }
1198  }
1199  formWidget = container;
1200  }
1201 
1202  // Autogenerate Layout
1203  // If there is still no layout loaded (defined as autogenerate or other methods failed)
1204  if ( !formWidget )
1205  {
1206  formWidget = new QWidget( this );
1207  QGridLayout *gridLayout = new QGridLayout( formWidget );
1208  formWidget->setLayout( gridLayout );
1209 
1210  if ( mContext.formMode() != QgsAttributeEditorContext::Embed )
1211  {
1212  // put the form into a scroll area to nicely handle cases with lots of attributes
1213  QgsScrollArea *scrollArea = new QgsScrollArea( this );
1214  scrollArea->setWidget( formWidget );
1215  scrollArea->setWidgetResizable( true );
1216  scrollArea->setFrameShape( QFrame::NoFrame );
1217  scrollArea->setFrameShadow( QFrame::Plain );
1218  scrollArea->setFocusProxy( this );
1219  layout->addWidget( scrollArea );
1220  }
1221  else
1222  {
1223  layout->addWidget( formWidget );
1224  }
1225 
1226  int row = 0;
1227  Q_FOREACH ( const QgsField &field, mLayer->fields().toList() )
1228  {
1229  int idx = mLayer->fields().lookupField( field.name() );
1230  if ( idx < 0 )
1231  continue;
1232 
1233  //show attribute alias if available
1234  QString fieldName = mLayer->attributeDisplayName( idx );
1235 
1236  const QgsEditorWidgetSetup widgetSetup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, field.name() );
1237 
1238  if ( widgetSetup.type() == QLatin1String( "Hidden" ) )
1239  continue;
1240 
1241  bool labelOnTop = mLayer->editFormConfig().labelOnTop( idx );
1242 
1243  // This will also create the widget
1244  QLabel *l = new QLabel( fieldName );
1245  QgsEditorWidgetWrapper *eww = QgsEditorWidgetRegistry::instance()->create( widgetSetup.type(), mLayer, idx, widgetSetup.config(), nullptr, this, mContext );
1246 
1247  QWidget *w = nullptr;
1248  if ( eww )
1249  {
1250  QgsAttributeFormEditorWidget *formWidget = new QgsAttributeFormEditorWidget( eww, this );
1251  w = formWidget;
1252  mFormEditorWidgets.insert( idx, formWidget );
1253  formWidget->createSearchWidgetWrappers( widgetSetup.type(), idx, widgetSetup.config(), mContext );
1254 
1255  l->setBuddy( eww->widget() );
1256  }
1257  else
1258  {
1259  w = new QLabel( QStringLiteral( "<p style=\"color: red; font-style: italic;\">Failed to create widget with type '%1'</p>" ).arg( widgetSetup.type() ) );
1260  }
1261 
1262 
1263  if ( w )
1264  w->setObjectName( field.name() );
1265 
1266  if ( eww )
1267  addWidgetWrapper( eww );
1268 
1269  if ( labelOnTop )
1270  {
1271  gridLayout->addWidget( l, row++, 0, 1, 2 );
1272  gridLayout->addWidget( w, row++, 0, 1, 2 );
1273  }
1274  else
1275  {
1276  gridLayout->addWidget( l, row, 0 );
1277  gridLayout->addWidget( w, row++, 1 );
1278  }
1279  }
1280 
1281  Q_FOREACH ( const QgsRelation &rel, QgsProject::instance()->relationManager()->referencedRelations( mLayer ) )
1282  {
1283  QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( mLayer, rel, nullptr, this );
1284  const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, rel.id() );
1285  rww->setConfig( setup.config() );
1286  rww->setContext( mContext );
1287  gridLayout->addWidget( rww->widget(), row++, 0, 1, 2 );
1288  mWidgets.append( rww );
1289  }
1290 
1291  if ( QgsProject::instance()->relationManager()->referencedRelations( mLayer ).isEmpty() )
1292  {
1293  QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1294  gridLayout->addItem( spacerItem, row, 0 );
1295  gridLayout->setRowStretch( row, 1 );
1296  row++;
1297  }
1298  }
1299 
1300  if ( !mButtonBox )
1301  {
1302  mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1303  mButtonBox->setObjectName( QStringLiteral( "buttonBox" ) );
1304  layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
1305  }
1306  mButtonBox->setVisible( buttonBoxVisible );
1307 
1308  if ( !mSearchButtonBox )
1309  {
1310  mSearchButtonBox = new QWidget();
1311  QHBoxLayout *boxLayout = new QHBoxLayout();
1312  boxLayout->setMargin( 0 );
1313  boxLayout->setContentsMargins( 0, 0, 0, 0 );
1314  mSearchButtonBox->setLayout( boxLayout );
1315  mSearchButtonBox->setObjectName( QStringLiteral( "searchButtonBox" ) );
1316 
1317  QPushButton *clearButton = new QPushButton( tr( "&Reset form" ), mSearchButtonBox );
1318  connect( clearButton, &QPushButton::clicked, this, &QgsAttributeForm::resetSearch );
1319  boxLayout->addWidget( clearButton );
1320  boxLayout->addStretch( 1 );
1321 
1322  QPushButton *zoomButton = new QPushButton();
1323  zoomButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1324  zoomButton->setText( tr( "&Zoom to features" ) );
1325  connect( zoomButton, &QToolButton::clicked, this, &QgsAttributeForm::searchZoomTo );
1326  boxLayout->addWidget( zoomButton );
1327 
1328  QToolButton *selectButton = new QToolButton();
1329  selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1330  selectButton->setText( tr( "&Select features" ) );
1331  selectButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
1332  selectButton->setPopupMode( QToolButton::MenuButtonPopup );
1333  selectButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
1334  connect( selectButton, &QToolButton::clicked, this, &QgsAttributeForm::searchSetSelection );
1335  QMenu *selectMenu = new QMenu( selectButton );
1336  QAction *selectAction = new QAction( tr( "Select features" ), selectMenu );
1337  selectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFormSelect.svg" ) ) );
1338  connect( selectAction, &QAction::triggered, this, &QgsAttributeForm::searchSetSelection );
1339  selectMenu->addAction( selectAction );
1340  QAction *addSelectAction = new QAction( tr( "Add to current selection" ), selectMenu );
1341  addSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectAdd.svg" ) ) );
1342  connect( addSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchAddToSelection );
1343  selectMenu->addAction( addSelectAction );
1344  QAction *deselectAction = new QAction( tr( "Remove from current selection" ), selectMenu );
1345  deselectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectRemove.svg" ) ) );
1346  connect( deselectAction, &QAction::triggered, this, &QgsAttributeForm::searchRemoveFromSelection );
1347  selectMenu->addAction( deselectAction );
1348  QAction *filterSelectAction = new QAction( tr( "Filter current selection" ), selectMenu );
1349  filterSelectAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSelectIntersect.svg" ) ) );
1350  connect( filterSelectAction, &QAction::triggered, this, &QgsAttributeForm::searchIntersectSelection );
1351  selectMenu->addAction( filterSelectAction );
1352  selectButton->setMenu( selectMenu );
1353  boxLayout->addWidget( selectButton );
1354 
1355  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
1356  {
1357  QToolButton *filterButton = new QToolButton();
1358  filterButton->setText( tr( "Filter features" ) );
1359  filterButton->setPopupMode( QToolButton::MenuButtonPopup );
1360  filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1361  connect( filterButton, &QToolButton::clicked, this, &QgsAttributeForm::filterTriggered );
1362  QMenu *filterMenu = new QMenu( filterButton );
1363  QAction *filterAndAction = new QAction( tr( "Filter within (\"AND\")" ), filterMenu );
1364  connect( filterAndAction, &QAction::triggered, this, &QgsAttributeForm::filterAndTriggered );
1365  filterMenu->addAction( filterAndAction );
1366  QAction *filterOrAction = new QAction( tr( "Extend filter (\"OR\")" ), filterMenu );
1367  connect( filterOrAction, &QAction::triggered, this, &QgsAttributeForm::filterOrTriggered );
1368  filterMenu->addAction( filterOrAction );
1369  filterButton->setMenu( filterMenu );
1370  boxLayout->addWidget( filterButton );
1371  }
1372  else
1373  {
1374  QPushButton *closeButton = new QPushButton( tr( "Close" ), mSearchButtonBox );
1375  connect( closeButton, &QPushButton::clicked, this, &QgsAttributeForm::closed );
1376  closeButton->setShortcut( Qt::Key_Escape );
1377  boxLayout->addWidget( closeButton );
1378  }
1379 
1380  layout->addWidget( mSearchButtonBox );
1381  }
1382  mSearchButtonBox->setVisible( mMode == SearchMode );
1383 
1384  afterWidgetInit();
1385 
1386  connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsAttributeForm::save );
1387  connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsAttributeForm::resetValues );
1388 
1389  connect( mLayer, &QgsVectorLayer::editingStarted, this, &QgsAttributeForm::synchronizeEnabledState );
1390  connect( mLayer, &QgsVectorLayer::editingStopped, this, &QgsAttributeForm::synchronizeEnabledState );
1391 
1392  Q_FOREACH ( QgsAttributeFormInterface *iface, mInterfaces )
1393  {
1394  iface->initForm();
1395  }
1396 
1397  if ( mContext.formMode() == QgsAttributeEditorContext::Embed || mMode == SearchMode )
1398  {
1399  hideButtonBox();
1400  }
1401 
1402  QApplication::restoreOverrideCursor();
1403 }
1404 
1405 void QgsAttributeForm::cleanPython()
1406 {
1407  if ( !mPyFormVarName.isNull() )
1408  {
1409  QString expr = QStringLiteral( "if locals().has_key('%1'): del %1\n" ).arg( mPyFormVarName );
1410  QgsPythonRunner::run( expr );
1411  }
1412 }
1413 
1414 void QgsAttributeForm::initPython()
1415 {
1416  cleanPython();
1417 
1418  // Init Python, if init function is not empty and the combo indicates
1419  // the source for the function code
1420  if ( !mLayer->editFormConfig().initFunction().isEmpty()
1422  {
1423 
1424  QString initFunction = mLayer->editFormConfig().initFunction();
1425  QString initFilePath = mLayer->editFormConfig().initFilePath();
1426  QString initCode;
1427 
1428  switch ( mLayer->editFormConfig().initCodeSource() )
1429  {
1431  if ( ! initFilePath.isEmpty() )
1432  {
1433  QFile inputFile( initFilePath );
1434 
1435  if ( inputFile.open( QFile::ReadOnly ) )
1436  {
1437  // Read it into a string
1438  QTextStream inf( &inputFile );
1439  initCode = inf.readAll();
1440  inputFile.close();
1441  }
1442  else // The file couldn't be opened
1443  {
1444  QgsLogger::warning( QStringLiteral( "The external python file path %1 could not be opened!" ).arg( initFilePath ) );
1445  }
1446  }
1447  else
1448  {
1449  QgsLogger::warning( QStringLiteral( "The external python file path is empty!" ) );
1450  }
1451  break;
1452 
1454  initCode = mLayer->editFormConfig().initCode();
1455  if ( initCode.isEmpty() )
1456  {
1457  QgsLogger::warning( QStringLiteral( "The python code provided in the dialog is empty!" ) );
1458  }
1459  break;
1460 
1463  default:
1464  // Nothing to do: the function code should be already in the environment
1465  break;
1466  }
1467 
1468  // If we have a function code, run it
1469  if ( ! initCode.isEmpty() )
1470  {
1471  QgsPythonRunner::run( initCode );
1472  }
1473 
1474  QgsPythonRunner::run( QStringLiteral( "import inspect" ) );
1475  QString numArgs;
1476 
1477  // Check for eval result
1478  if ( QgsPythonRunner::eval( QStringLiteral( "len(inspect.getargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
1479  {
1480  static int sFormId = 0;
1481  mPyFormVarName = QStringLiteral( "_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
1482 
1483  QString form = QStringLiteral( "%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
1484  .arg( mPyFormVarName )
1485  .arg( ( quint64 ) this );
1486 
1487  QgsPythonRunner::run( form );
1488 
1489  QgsDebugMsg( QString( "running featureForm init: %1" ).arg( mPyFormVarName ) );
1490 
1491  // Legacy
1492  if ( numArgs == QLatin1String( "3" ) )
1493  {
1494  addInterface( new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName, this ) );
1495  }
1496  else
1497  {
1498  // If we get here, it means that the function doesn't accept three arguments
1499  QMessageBox msgBox;
1500  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 ) );
1501  msgBox.exec();
1502 #if 0
1503  QString expr = QString( "%1(%2)" )
1504  .arg( mLayer->editFormInit() )
1505  .arg( mPyFormVarName );
1506  QgsAttributeFormInterface *iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface *>( expr, "QgsAttributeFormInterface" );
1507  if ( iface )
1508  addInterface( iface );
1509 #endif
1510  }
1511  }
1512  else
1513  {
1514  // If we get here, it means that inspect couldn't find the function
1515  QMessageBox msgBox;
1516  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 ) );
1517  msgBox.exec();
1518  }
1519  }
1520 }
1521 
1522 QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement *widgetDef, QWidget *parent, QgsVectorLayer *vl, QgsAttributeEditorContext &context )
1523 {
1524  WidgetInfo newWidgetInfo;
1525 
1526  switch ( widgetDef->type() )
1527  {
1529  {
1530  const QgsAttributeEditorField *fieldDef = dynamic_cast<const QgsAttributeEditorField *>( widgetDef );
1531  if ( !fieldDef )
1532  break;
1533 
1534  int fldIdx = vl->fields().lookupField( fieldDef->name() );
1535  if ( fldIdx < vl->fields().count() && fldIdx >= 0 )
1536  {
1537  const QgsEditorWidgetSetup widgetSetup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, fieldDef->name() );
1538 
1539  QgsEditorWidgetWrapper *eww = QgsEditorWidgetRegistry::instance()->create( widgetSetup.type(), mLayer, fldIdx, widgetSetup.config(), nullptr, this, mContext );
1541  mFormEditorWidgets.insert( fldIdx, w );
1542 
1543  w->createSearchWidgetWrappers( widgetSetup.type(), fldIdx, widgetSetup.config(), mContext );
1544 
1545  newWidgetInfo.widget = w;
1546  addWidgetWrapper( eww );
1547 
1548  newWidgetInfo.widget->setObjectName( mLayer->fields().at( fldIdx ).name() );
1549  }
1550 
1551  newWidgetInfo.labelOnTop = mLayer->editFormConfig().labelOnTop( fieldDef->idx() );
1552  newWidgetInfo.labelText = mLayer->attributeDisplayName( fieldDef->idx() );
1553  newWidgetInfo.showLabel = widgetDef->showLabel();
1554 
1555  break;
1556  }
1557 
1559  {
1560  const QgsAttributeEditorRelation *relDef = static_cast<const QgsAttributeEditorRelation *>( widgetDef );
1561 
1562  QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( mLayer, relDef->relation(), nullptr, this );
1563  const QgsEditorWidgetSetup widgetSetup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, relDef->relation().id() );
1564  rww->setConfig( widgetSetup.config() );
1565  rww->setContext( context );
1566  newWidgetInfo.widget = rww->widget();
1567  rww->setShowLabel( relDef->showLabel() );
1568  rww->setShowLinkButton( relDef->showLinkButton() );
1569  rww->setShowUnlinkButton( relDef->showUnlinkButton() );
1570  mWidgets.append( rww );
1571  newWidgetInfo.labelText = QString::null;
1572  newWidgetInfo.labelOnTop = true;
1573  break;
1574  }
1575 
1577  {
1578  const QgsAttributeEditorContainer *container = dynamic_cast<const QgsAttributeEditorContainer *>( widgetDef );
1579  if ( !container )
1580  break;
1581 
1582  int columnCount = container->columnCount();
1583 
1584  if ( columnCount <= 0 )
1585  columnCount = 1;
1586 
1587  QWidget *myContainer = nullptr;
1588  if ( container->isGroupBox() )
1589  {
1590  QGroupBox *groupBox = new QGroupBox( parent );
1591  if ( container->showLabel() )
1592  groupBox->setTitle( container->name() );
1593  myContainer = groupBox;
1594  newWidgetInfo.widget = myContainer;
1595  }
1596  else
1597  {
1598  myContainer = new QWidget();
1599 
1600  if ( context.formMode() != QgsAttributeEditorContext::Embed )
1601  {
1602  QgsScrollArea *scrollArea = new QgsScrollArea( parent );
1603 
1604  scrollArea->setWidget( myContainer );
1605  scrollArea->setWidgetResizable( true );
1606  scrollArea->setFrameShape( QFrame::NoFrame );
1607 
1608  newWidgetInfo.widget = scrollArea;
1609  }
1610  else
1611  {
1612  newWidgetInfo.widget = myContainer;
1613  }
1614  }
1615 
1616  QGridLayout *gbLayout = new QGridLayout();
1617  myContainer->setLayout( gbLayout );
1618 
1619  int row = 0;
1620  int column = 0;
1621 
1622  QList<QgsAttributeEditorElement *> children = container->children();
1623 
1624  Q_FOREACH ( QgsAttributeEditorElement *childDef, children )
1625  {
1626  WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
1627 
1628  if ( childDef->type() == QgsAttributeEditorElement::AeTypeContainer )
1629  {
1630  QgsAttributeEditorContainer *containerDef = static_cast<QgsAttributeEditorContainer *>( childDef );
1631  registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().data() ) );
1632  }
1633 
1634  if ( widgetInfo.labelText.isNull() )
1635  {
1636  gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1637  column += 2;
1638  }
1639  else
1640  {
1641  QLabel *mypLabel = new QLabel( widgetInfo.labelText );
1642  if ( columnCount > 1 && !widgetInfo.labelOnTop )
1643  {
1644  mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1645  }
1646 
1647  mypLabel->setBuddy( widgetInfo.widget );
1648 
1649  if ( widgetInfo.labelOnTop )
1650  {
1651  QVBoxLayout *c = new QVBoxLayout();
1652  mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1653  c->layout()->addWidget( mypLabel );
1654  c->layout()->addWidget( widgetInfo.widget );
1655  gbLayout->addLayout( c, row, column, 1, 2 );
1656  column += 2;
1657  }
1658  else
1659  {
1660  gbLayout->addWidget( mypLabel, row, column++ );
1661  gbLayout->addWidget( widgetInfo.widget, row, column++ );
1662  }
1663  }
1664 
1665  if ( column >= columnCount * 2 )
1666  {
1667  column = 0;
1668  row += 1;
1669  }
1670  }
1671  QWidget *spacer = new QWidget();
1672  spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
1673  gbLayout->addWidget( spacer, ++row, 0 );
1674  gbLayout->setRowStretch( row, 1 );
1675 
1676  newWidgetInfo.labelText = QString::null;
1677  newWidgetInfo.labelOnTop = true;
1678  break;
1679  }
1680 
1681  default:
1682  QgsDebugMsg( "Unknown attribute editor widget type encountered..." );
1683  break;
1684  }
1685 
1686  newWidgetInfo.showLabel = widgetDef->showLabel();
1687 
1688  return newWidgetInfo;
1689 }
1690 
1691 void QgsAttributeForm::addWidgetWrapper( QgsEditorWidgetWrapper *eww )
1692 {
1693  Q_FOREACH ( QgsWidgetWrapper *ww, mWidgets )
1694  {
1695  QgsEditorWidgetWrapper *meww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1696  if ( meww )
1697  {
1698  if ( meww->field() == eww->field() )
1699  {
1700  connect( meww, SIGNAL( valueChanged( QVariant ) ), eww, SLOT( setValue( QVariant ) ) );
1701  connect( eww, SIGNAL( valueChanged( QVariant ) ), meww, SLOT( setValue( QVariant ) ) );
1702  break;
1703  }
1704  }
1705  }
1706 
1707  mWidgets.append( eww );
1708 }
1709 
1710 void QgsAttributeForm::createWrappers()
1711 {
1712  QList<QWidget *> myWidgets = findChildren<QWidget *>();
1713  const QList<QgsField> fields = mLayer->fields().toList();
1714 
1715  Q_FOREACH ( QWidget *myWidget, myWidgets )
1716  {
1717  // Check the widget's properties for a relation definition
1718  QVariant vRel = myWidget->property( "qgisRelation" );
1719  if ( vRel.isValid() )
1720  {
1722  QgsRelation relation = relMgr->relation( vRel.toString() );
1723  if ( relation.isValid() )
1724  {
1725  QgsRelationWidgetWrapper *rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
1726  const QgsEditorWidgetSetup widgetSetup = QgsEditorWidgetRegistry::instance()->findBest( mLayer, relation.id() );
1727  rww->setConfig( widgetSetup.config() );
1728  rww->setContext( mContext );
1729  rww->widget(); // Will initialize the widget
1730  mWidgets.append( rww );
1731  }
1732  }
1733  else
1734  {
1735  Q_FOREACH ( const QgsField &field, fields )
1736  {
1737  if ( field.name() == myWidget->objectName() )
1738  {
1739  int idx = mLayer->fields().lookupField( field.name() );
1740 
1741  QgsEditorWidgetWrapper *eww = QgsEditorWidgetRegistry::instance()->create( mLayer, idx, myWidget, this, mContext );
1742  addWidgetWrapper( eww );
1743  }
1744  }
1745  }
1746  }
1747 }
1748 
1749 void QgsAttributeForm::afterWidgetInit()
1750 {
1751  bool isFirstEww = true;
1752 
1753  Q_FOREACH ( QgsWidgetWrapper *ww, mWidgets )
1754  {
1755  QgsEditorWidgetWrapper *eww = qobject_cast<QgsEditorWidgetWrapper *>( ww );
1756 
1757  if ( eww )
1758  {
1759  if ( isFirstEww )
1760  {
1761  setFocusProxy( eww->widget() );
1762  isFirstEww = false;
1763  }
1764 
1765  connect( eww, SIGNAL( valueChanged( const QVariant & ) ), this, SLOT( onAttributeChanged( const QVariant & ) ) );
1766  connect( eww, &QgsEditorWidgetWrapper::constraintStatusChanged, this, &QgsAttributeForm::onConstraintStatusChanged );
1767  }
1768  }
1769 
1770  // Update buddy widget list
1771  mBuddyMap.clear();
1772  QList<QLabel *> labels = findChildren<QLabel *>();
1773 
1774  Q_FOREACH ( QLabel *label, labels )
1775  {
1776  if ( label->buddy() )
1777  mBuddyMap.insert( label->buddy(), label );
1778  }
1779 }
1780 
1781 
1782 bool QgsAttributeForm::eventFilter( QObject *object, QEvent *e )
1783 {
1784  Q_UNUSED( object )
1785 
1786  if ( e->type() == QEvent::KeyPress )
1787  {
1788  QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>( e );
1789  if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
1790  {
1791  // Re-emit to this form so it will be forwarded to parent
1792  event( e );
1793  return true;
1794  }
1795  }
1796 
1797  return false;
1798 }
1799 
1800 void QgsAttributeForm::scanForEqualAttributes( QgsFeatureIterator &fit, QSet< int > &mixedValueFields, QHash< int, QVariant > &fieldSharedValues ) const
1801 {
1802  mixedValueFields.clear();
1803  fieldSharedValues.clear();
1804 
1805  QgsFeature f;
1806  bool first = true;
1807  while ( fit.nextFeature( f ) )
1808  {
1809  for ( int i = 0; i < mLayer->fields().count(); ++i )
1810  {
1811  if ( mixedValueFields.contains( i ) )
1812  continue;
1813 
1814  if ( first )
1815  {
1816  fieldSharedValues[i] = f.attribute( i );
1817  }
1818  else
1819  {
1820  if ( fieldSharedValues.value( i ) != f.attribute( i ) )
1821  {
1822  fieldSharedValues.remove( i );
1823  mixedValueFields.insert( i );
1824  }
1825  }
1826  }
1827  first = false;
1828 
1829  if ( mixedValueFields.count() == mLayer->fields().count() )
1830  {
1831  // all attributes are mixed, no need to keep scanning
1832  break;
1833  }
1834  }
1835 }
1836 
1837 
1838 void QgsAttributeForm::layerSelectionChanged()
1839 {
1840  switch ( mMode )
1841  {
1842  case SingleEditMode:
1843  case AddFeatureMode:
1844  case SearchMode:
1845  break;
1846 
1847  case MultiEditMode:
1848  resetMultiEdit( true );
1849  break;
1850  }
1851 }
1852 
1854 {
1855  mIsSettingMultiEditFeatures = true;
1856  mMultiEditFeatureIds = fids;
1857 
1858  if ( fids.isEmpty() )
1859  {
1860  // no selected features
1861  QMap< int, QgsAttributeFormEditorWidget * >::const_iterator wIt = mFormEditorWidgets.constBegin();
1862  for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
1863  {
1864  wIt.value()->initialize( QVariant() );
1865  }
1866  mIsSettingMultiEditFeatures = false;
1867  return;
1868  }
1869 
1870  QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFids( fids ) );
1871 
1872  // Scan through all features to determine which attributes are initially the same
1873  QSet< int > mixedValueFields;
1874  QHash< int, QVariant > fieldSharedValues;
1875  scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
1876 
1877  // also fetch just first feature
1878  fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFid( *fids.constBegin() ) );
1879  QgsFeature firstFeature;
1880  fit.nextFeature( firstFeature );
1881 
1882  Q_FOREACH ( int field, mixedValueFields )
1883  {
1884  if ( QgsAttributeFormEditorWidget *w = mFormEditorWidgets.value( field, nullptr ) )
1885  {
1886  w->initialize( firstFeature.attribute( field ), true );
1887  }
1888  }
1889  QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
1890  for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
1891  {
1892  if ( QgsAttributeFormEditorWidget *w = mFormEditorWidgets.value( sharedValueIt.key(), nullptr ) )
1893  {
1894  w->initialize( sharedValueIt.value(), false );
1895  }
1896  }
1897  mIsSettingMultiEditFeatures = false;
1898 }
1899 
1901 {
1902  if ( mOwnsMessageBar )
1903  delete mMessageBar;
1904  mOwnsMessageBar = false;
1905  mMessageBar = messageBar;
1906 }
1907 
1908 int QgsAttributeForm::messageTimeout()
1909 {
1910  QgsSettings settings;
1911  return settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt();
1912 }
1913 
1914 void QgsAttributeForm::ContainerInformation::apply( QgsExpressionContext *expressionContext )
1915 {
1916  bool newVisibility = expression.evaluate( expressionContext ).toBool();
1917 
1918  if ( newVisibility != isVisible )
1919  {
1920  if ( tabWidget )
1921  {
1922  tabWidget->setTabVisible( widget, newVisibility );
1923  }
1924  else
1925  {
1926  widget->setVisible( newVisibility );
1927  }
1928 
1929  isVisible = newVisibility;
1930  }
1931 }
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:188
Class for parsing and evaluation of expressions (formerly called "search strings").
Load the python code from an external file.
QgsFeatureId id
Definition: qgsfeature.h:140
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...
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.
virtual 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:167
QString name
Definition: qgsfield.h:53
QMap< int, QVariant > QgsAttributeMap
Definition: qgsfeature.h:45
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:90
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:51
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:36
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeature.h:358
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:39
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:43
Widget failed at least one hard (enforced) constraint.
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:139
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.
QSet< QString > referencedColumns() const
Get list of columns referenced by the expression.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:136
bool addFeature(QgsFeature &feature, bool alsoUpdateExtent=true)
Adds a feature.
PythonInitCodeSource initCodeSource() const
Return python code source for edit form initialization (if it shall be loaded from a file...
QgsFields fields
Definition: qgsfeature.h:142
virtual bool isEditable() const override
Returns true if the provider is in editing mode.
static QgsEditorWidgetRegistry * instance()
This class is a singleton and has therefore to be accessed with this method instead of a constructor...
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.
QgsFields fields() const
Returns the list of fields of this layer.
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:90
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.
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:86
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.
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...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const
Query the layer for features specified in request.
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).
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.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:45
QgsRelationManager relationManager
Definition: qgsproject.h:85
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.
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.
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.
This class helps to support legacy open form scripts to be compatible with the new QgsAttributeForm s...
Set selection, removing any existing selection.
void featureSaved(const QgsFeature &feature)
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:193
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:43
void constraintStatusChanged(const QString &constraint, const QString &desc, const QString &err, ConstraintResult status)
Emit this signal when the constraint status changed.
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.
bool allowCustomUi() const
Returns true if the attribute editor should permit use of custom UI forms.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a python statement.
QgsEditorWidgetSetup findBest(const QgsVectorLayer *vl, const QString &fieldName) const
Find the best editor widget and its configuration for a given field.
Load a .ui file for the layout. Needs to be configured.
Holder for the widget type and its configuration for a field.
This class manages a set of relations between layers.
int columnCount() const
Get the number of columns in this group.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:359
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:120
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.
This is a container for attribute editors, used to group them visually in the attribute form if it is...
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=Section::NoSection) const
Returns the value for setting key.
void addInterface(QgsAttributeFormInterface *iface)
Takes ownership.
qint64 QgsFeatureId
Definition: qgsfeature.h:33
#define FID_IS_NEW(fid)
Definition: qgsfeature.h:39
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 ...
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;.
QgsVectorDataProvider * dataProvider()
Returns the data provider.
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 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:40
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: qgsfeature.h:56
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:94
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:267
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
Manages an editor widget Widget and wrapper share the same parent.
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.
QgsAttributes attributes
Definition: qgsfeature.h:141
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.