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