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