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