QGIS API Documentation  2.14.11-Essen
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"
26 
27 #include <QDir>
28 #include <QTextStream>
29 #include <QFileInfo>
30 #include <QFile>
31 #include <QFormLayout>
32 #include <QGridLayout>
33 #include <QGroupBox>
34 #include <QKeyEvent>
35 #include <QLabel>
36 #include <QPushButton>
37 #include <QScrollArea>
38 #include <QTabWidget>
39 #include <QUiLoader>
40 #include <QMessageBox>
41 
42 int QgsAttributeForm::sFormCounter = 0;
43 
45  : QWidget( parent )
46  , mLayer( vl )
47  , mContext( context )
48  , mButtonBox( nullptr )
49  , mFormNr( sFormCounter++ )
50  , mIsSaving( false )
51  , mIsAddDialog( false )
52  , mPreventFeatureRefresh( false )
53  , mEditCommandMessage( tr( "Attributes changed" ) )
54 {
55  init();
56  initPython();
57  setFeature( feature );
58 
59  // Using attributeAdded() attributeDeleted() are not emitted on all fields changes (e.g. layer fields changed,
60  // joined fields changed) -> use updatedFields() instead
61 #if 0
62  connect( vl, SIGNAL( attributeAdded( int ) ), this, SLOT( onAttributeAdded( int ) ) );
63  connect( vl, SIGNAL( attributeDeleted( int ) ), this, SLOT( onAttributeDeleted( int ) ) );
64 #endif
65  connect( vl, SIGNAL( updatedFields() ), this, SLOT( onUpdatedFields() ) );
66  connect( vl, SIGNAL( beforeAddingExpressionField( QString ) ), this, SLOT( preventFeatureRefresh() ) );
67  connect( vl, SIGNAL( beforeRemovingExpressionField( int ) ), this, SLOT( preventFeatureRefresh() ) );
68 }
69 
71 {
72  cleanPython();
73  qDeleteAll( mInterfaces );
74 }
75 
77 {
78  mButtonBox->hide();
79 
80  // Make sure that changes are taken into account if somebody tries to figure out if there have been some
81  if ( !mIsAddDialog )
82  connect( mLayer, SIGNAL( beforeModifiedCheck() ), this, SLOT( save() ) );
83 }
84 
86 {
87  mButtonBox->show();
88 
89  disconnect( mLayer, SIGNAL( beforeModifiedCheck() ), this, SLOT( save() ) );
90 }
91 
93 {
94  disconnect( mButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
95  disconnect( mButtonBox, SIGNAL( rejected() ), this, SLOT( resetValues() ) );
96 }
97 
99 {
100  mInterfaces.append( iface );
101 }
102 
104 {
105  return mFeature.isValid() && mLayer->isEditable();
106 }
107 
108 void QgsAttributeForm::setIsAddDialog( bool isAddDialog )
109 {
110  mIsAddDialog = isAddDialog;
111 
112  synchronizeEnabledState();
113 }
114 
115 void QgsAttributeForm::changeAttribute( const QString& field, const QVariant& value )
116 {
117  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
118  {
119  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
120  if ( eww && eww->field().name() == field )
121  {
122  eww->setValue( value );
123  }
124  }
125 }
126 
128 {
129  mFeature = feature;
130 
131  resetValues();
132 
133  synchronizeEnabledState();
134 
135  Q_FOREACH ( QgsAttributeFormInterface* iface, mInterfaces )
136  {
137  iface->featureChanged();
138  }
139 }
140 
142 {
143  if ( mIsSaving )
144  return true;
145 
146  mIsSaving = true;
147 
148  bool changedLayer = false;
149 
150  bool success = true;
151 
152  emit beforeSave( success );
153 
154  // Somebody wants to prevent this form from saving
155  if ( !success )
156  return false;
157 
158  QgsFeature updatedFeature = QgsFeature( mFeature );
159 
160  if ( mFeature.isValid() || mIsAddDialog )
161  {
162  bool doUpdate = false;
163 
164  // An add dialog should perform an action by default
165  // and not only if attributes have "changed"
166  if ( mIsAddDialog )
167  doUpdate = true;
168 
169  QgsAttributes src = mFeature.attributes();
170  QgsAttributes dst = mFeature.attributes();
171 
172  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
173  {
174  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
175  if ( eww )
176  {
177  QVariant dstVar = dst.at( eww->fieldIdx() );
178  QVariant srcVar = eww->value();
179 
180  // need to check dstVar.isNull() != srcVar.isNull()
181  // otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
182  // be careful- sometimes two null qvariants will be reported as not equal!! (eg different types)
183  bool changed = ( dstVar != srcVar && !dstVar.isNull() && !srcVar.isNull() )
184  || ( dstVar.isNull() != srcVar.isNull() );
185  if ( changed && srcVar.isValid()
186  && !mLayer->editFormConfig()->readOnly( eww->fieldIdx() ) )
187  {
188  dst[eww->fieldIdx()] = srcVar;
189 
190  doUpdate = true;
191  }
192  }
193  }
194 
195  updatedFeature.setAttributes( dst );
196 
197  Q_FOREACH ( QgsAttributeFormInterface* iface, mInterfaces )
198  {
199  if ( !iface->acceptChanges( updatedFeature ) )
200  {
201  doUpdate = false;
202  }
203  }
204 
205  if ( doUpdate )
206  {
207  if ( mIsAddDialog )
208  {
209  mFeature.setValid( true );
210  mLayer->beginEditCommand( mEditCommandMessage );
211  bool res = mLayer->addFeature( updatedFeature );
212  if ( res )
213  {
214  mFeature.setAttributes( updatedFeature.attributes() );
215  mLayer->endEditCommand();
216  mIsAddDialog = false;
217  changedLayer = true;
218  }
219  else
220  mLayer->destroyEditCommand();
221  }
222  else
223  {
224  mLayer->beginEditCommand( mEditCommandMessage );
225 
226  int n = 0;
227  for ( int i = 0; i < dst.count(); ++i )
228  {
229  if (( dst.at( i ) == src.at( i ) && dst.at( i ).isNull() == src.at( i ).isNull() ) // If field is not changed...
230  || !dst.at( i ).isValid() // or the widget returns invalid (== do not change)
231  || mLayer->editFormConfig()->readOnly( i ) ) // or the field cannot be edited ...
232  {
233  continue;
234  }
235 
236  QgsDebugMsg( QString( "Updating field %1" ).arg( i ) );
237  QgsDebugMsg( QString( "dst:'%1' (type:%2, isNull:%3, isValid:%4)" )
238  .arg( dst.at( i ).toString(), dst.at( i ).typeName() ).arg( dst.at( i ).isNull() ).arg( dst.at( i ).isValid() ) );
239  QgsDebugMsg( QString( "src:'%1' (type:%2, isNull:%3, isValid:%4)" )
240  .arg( src.at( i ).toString(), src.at( i ).typeName() ).arg( src.at( i ).isNull() ).arg( src.at( i ).isValid() ) );
241 
242  success &= mLayer->changeAttributeValue( mFeature.id(), i, dst.at( i ), src.at( i ) );
243  n++;
244  }
245 
246  if ( success && n > 0 )
247  {
248  mLayer->endEditCommand();
249  mFeature.setAttributes( dst );
250  changedLayer = true;
251  }
252  else
253  {
254  mLayer->destroyEditCommand();
255  }
256  }
257  }
258  }
259 
260  emit featureSaved( updatedFeature );
261 
262  // [MD] Refresh canvas only when absolutely necessary - it interferes with other stuff (#11361).
263  // This code should be revisited - and the signals should be fired (+ layer repainted)
264  // only when actually doing any changes. I am unsure if it is actually a good idea
265  // to call save() whenever some code asks for vector layer's modified status
266  // (which is the case when attribute table is open)
267  if ( changedLayer )
268  mLayer->triggerRepaint();
269 
270  mIsSaving = false;
271 
272  return success;
273 }
274 
276 {
277  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
278  {
279  ww->setFeature( mFeature );
280  }
281 }
282 
283 void QgsAttributeForm::onAttributeChanged( const QVariant& value )
284 {
285  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( sender() );
286 
287  Q_ASSERT( eww );
288 
289  emit attributeChanged( eww->field().name(), value );
290 }
291 
292 void QgsAttributeForm::onAttributeAdded( int idx )
293 {
294  mPreventFeatureRefresh = false;
295  if ( mFeature.isValid() )
296  {
297  QgsAttributes attrs = mFeature.attributes();
298  attrs.insert( idx, QVariant( layer()->fields().at( idx ).type() ) );
299  mFeature.setFields( layer()->fields() );
300  mFeature.setAttributes( attrs );
301  }
302  init();
303  setFeature( mFeature );
304 }
305 
306 void QgsAttributeForm::onAttributeDeleted( int idx )
307 {
308  mPreventFeatureRefresh = false;
309  if ( mFeature.isValid() )
310  {
311  QgsAttributes attrs = mFeature.attributes();
312  attrs.remove( idx );
313  mFeature.setFields( layer()->fields() );
314  mFeature.setAttributes( attrs );
315  }
316  init();
317  setFeature( mFeature );
318 }
319 
320 void QgsAttributeForm::onUpdatedFields()
321 {
322  mPreventFeatureRefresh = false;
323  if ( mFeature.isValid() )
324  {
325  QgsAttributes attrs( layer()->fields().size() );
326  for ( int i = 0; i < layer()->fields().size(); i++ )
327  {
328  int idx = mFeature.fields()->indexFromName( layer()->fields().at( i ).name() );
329  if ( idx != -1 )
330  {
331  attrs[i] = mFeature.attributes().at( idx );
332  if ( mFeature.attributes().at( idx ).type() != layer()->fields().at( i ).type() )
333  {
334  attrs[i].convert( layer()->fields().at( i ).type() );
335  }
336  }
337  else
338  {
339  attrs[i] = QVariant( layer()->fields().at( i ).type() );
340  }
341  }
342  mFeature.setFields( layer()->fields() );
343  mFeature.setAttributes( attrs );
344  }
345  init();
346  setFeature( mFeature );
347 }
348 
349 void QgsAttributeForm::preventFeatureRefresh()
350 {
351  mPreventFeatureRefresh = true;
352 }
353 
355 {
356  if ( mPreventFeatureRefresh || mLayer->isEditable() || !mFeature.isValid() )
357  return;
358 
359  // reload feature if layer changed although not editable
360  // (datasource probably changed bypassing QgsVectorLayer)
361  if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeature.id() ) ).nextFeature( mFeature ) )
362  return;
363 
364  init();
365  setFeature( mFeature );
366 }
367 
368 void QgsAttributeForm::synchronizeEnabledState()
369 {
370  bool isEditable = ( mFeature.isValid() || mIsAddDialog ) && mLayer->isEditable();
371 
372  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
373  {
374  bool fieldEditable = true;
375  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
376  if ( eww )
377  {
378  fieldEditable = !mLayer->editFormConfig()->readOnly( eww->fieldIdx() ) &&
380  FID_IS_NEW( mFeature.id() ) );
381  }
382  ww->setEnabled( isEditable && fieldEditable );
383  }
384 
385  QPushButton* okButton = mButtonBox->button( QDialogButtonBox::Ok );
386  if ( okButton )
387  okButton->setEnabled( isEditable );
388 }
389 
390 void QgsAttributeForm::init()
391 {
392  QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
393 
394  // Cleanup of any previously shown widget, we start from scratch
395  QWidget* formWidget = nullptr;
396 
397  bool buttonBoxVisible = true;
398  // Cleanup button box but preserve visibility
399  if ( mButtonBox )
400  {
401  buttonBoxVisible = mButtonBox->isVisible();
402  delete mButtonBox;
403  mButtonBox = nullptr;
404  }
405 
406  qDeleteAll( mWidgets );
407  mWidgets.clear();
408 
409  while ( QWidget* w = this->findChild<QWidget*>() )
410  {
411  delete w;
412  }
413  delete layout();
414 
415  // Get a layout
416  setLayout( new QGridLayout( this ) );
417 
418  // Try to load Ui-File for layout
419  if ( mLayer->editFormConfig()->layout() == QgsEditFormConfig::UiFileLayout && !mLayer->editFormConfig()->uiForm().isEmpty() )
420  {
421  QFile file( mLayer->editFormConfig()->uiForm() );
422 
423  if ( file.open( QFile::ReadOnly ) )
424  {
425  QUiLoader loader;
426 
427  QFileInfo fi( mLayer->editFormConfig()->uiForm() );
428  loader.setWorkingDirectory( fi.dir() );
429  formWidget = loader.load( &file, this );
430  formWidget->setWindowFlags( Qt::Widget );
431  layout()->addWidget( formWidget );
432  formWidget->show();
433  file.close();
434  mButtonBox = findChild<QDialogButtonBox*>();
435  createWrappers();
436 
437  formWidget->installEventFilter( this );
438  }
439  }
440 
441  // Tab layout
442  if ( !formWidget && mLayer->editFormConfig()->layout() == QgsEditFormConfig::TabLayout )
443  {
444  QTabWidget* tabWidget = new QTabWidget();
445  layout()->addWidget( tabWidget );
446 
447  Q_FOREACH ( QgsAttributeEditorElement* widgDef, mLayer->editFormConfig()->tabs() )
448  {
449  QWidget* tabPage = new QWidget( tabWidget );
450 
451  tabWidget->addTab( tabPage, widgDef->name() );
452  QGridLayout* tabPageLayout = new QGridLayout();
453  tabPage->setLayout( tabPageLayout );
454 
456  {
457  QgsAttributeEditorContainer* containerDef = dynamic_cast<QgsAttributeEditorContainer*>( widgDef );
458  if ( !containerDef )
459  continue;
460 
461  containerDef->setIsGroupBox( false ); // Toplevel widgets are tabs not groupboxes
462  QString dummy1;
463  bool dummy2;
464  tabPageLayout->addWidget( createWidgetFromDef( widgDef, tabPage, mLayer, mContext, dummy1, dummy2 ) );
465  }
466  else
467  {
468  QgsDebugMsg( "No support for fields in attribute editor on top level" );
469  }
470  }
471  formWidget = tabWidget;
472  }
473 
474  // Autogenerate Layout
475  // If there is still no layout loaded (defined as autogenerate or other methods failed)
476  if ( !formWidget )
477  {
478  formWidget = new QWidget( this );
479  QGridLayout* gridLayout = new QGridLayout( formWidget );
480  formWidget->setLayout( gridLayout );
481 
482  // put the form into a scroll area to nicely handle cases with lots of attributes
483 
484  QScrollArea* scrollArea = new QScrollArea( this );
485  scrollArea->setWidget( formWidget );
486  scrollArea->setWidgetResizable( true );
487  scrollArea->setFrameShape( QFrame::NoFrame );
488  scrollArea->setFrameShadow( QFrame::Plain );
489  scrollArea->setFocusProxy( this );
490  layout()->addWidget( scrollArea );
491 
492  int row = 0;
493  Q_FOREACH ( const QgsField& field, mLayer->fields().toList() )
494  {
495  int idx = mLayer->fieldNameIndex( field.name() );
496  if ( idx < 0 )
497  continue;
498 
499  //show attribute alias if available
500  QString fieldName = mLayer->attributeDisplayName( idx );
501 
502  const QString widgetType = mLayer->editFormConfig()->widgetType( idx );
503 
504  if ( widgetType == "Hidden" )
505  continue;
506 
507  const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig()->widgetConfig( idx );
508  bool labelOnTop = mLayer->editFormConfig()->labelOnTop( idx );
509 
510  // This will also create the widget
511  QWidget *l = new QLabel( fieldName );
512  QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, idx, widgetConfig, nullptr, this, mContext );
513  QWidget *w = eww ? eww->widget() : new QLabel( QString( "<p style=\"color: red; font-style: italic;\">Failed to create widget with type '%1'</p>" ).arg( widgetType ) );
514 
515  if ( w )
516  w->setObjectName( field.name() );
517 
518  if ( eww )
519  addWidgetWrapper( eww );
520 
521  if ( labelOnTop )
522  {
523  gridLayout->addWidget( l, row++, 0, 1, 2 );
524  gridLayout->addWidget( w, row++, 0, 1, 2 );
525  }
526  else
527  {
528  gridLayout->addWidget( l, row, 0 );
529  gridLayout->addWidget( w, row++, 1 );
530  }
531  }
532 
533  Q_FOREACH ( const QgsRelation& rel, QgsProject::instance()->relationManager()->referencedRelations( mLayer ) )
534  {
535  QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, rel, nullptr, this );
536  QgsEditorWidgetConfig cfg = mLayer->editFormConfig()->widgetConfig( rel.id() );
537  rww->setConfig( cfg );
538  rww->setContext( mContext );
539  gridLayout->addWidget( rww->widget(), row++, 0, 1, 2 );
540  mWidgets.append( rww );
541  }
542 
543  if ( QgsProject::instance()->relationManager()->referencedRelations( mLayer ).isEmpty() )
544  {
545  QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
546  gridLayout->addItem( spacerItem, row++, 0 );
547  }
548  }
549 
550  if ( !mButtonBox )
551  {
552  mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
553  mButtonBox->setObjectName( "buttonBox" );
554  layout()->addWidget( mButtonBox );
555  }
556 
557  mButtonBox->setVisible( buttonBoxVisible );
558 
559  connectWrappers();
560 
561  connect( mButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
562  connect( mButtonBox, SIGNAL( rejected() ), this, SLOT( resetValues() ) );
563 
564  connect( mLayer, SIGNAL( editingStarted() ), this, SLOT( synchronizeEnabledState() ) );
565  connect( mLayer, SIGNAL( editingStopped() ), this, SLOT( synchronizeEnabledState() ) );
566 
567  Q_FOREACH ( QgsAttributeFormInterface* iface, mInterfaces )
568  {
569  iface->initForm();
570  }
572 }
573 
574 void QgsAttributeForm::cleanPython()
575 {
576  if ( !mPyFormVarName.isNull() )
577  {
578  QString expr = QString( "if locals().has_key('%1'): del %1\n" ).arg( mPyFormVarName );
579  QgsPythonRunner::run( expr );
580  }
581 }
582 
583 void QgsAttributeForm::initPython()
584 {
585  cleanPython();
586 
587  // Init Python, if init function is not empty and the combo indicates
588  // the source for the function code
589  if ( !mLayer->editFormConfig()->initFunction().isEmpty()
591  {
592 
593  QString initFunction = mLayer->editFormConfig()->initFunction();
594  QString initFilePath = mLayer->editFormConfig()->initFilePath();
595  QString initCode;
596 
597  switch ( mLayer->editFormConfig()->initCodeSource() )
598  {
600  if ( ! initFilePath.isEmpty() )
601  {
602  QFile inputFile( initFilePath );
603 
604  if ( inputFile.open( QFile::ReadOnly ) )
605  {
606  // Read it into a string
607  QTextStream inf( &inputFile );
608  initCode = inf.readAll();
609  inputFile.close();
610  }
611  else // The file couldn't be opened
612  {
613  QgsLogger::warning( QString( "The external python file path %1 could not be opened!" ).arg( initFilePath ) );
614  }
615  }
616  else
617  {
618  QgsLogger::warning( QString( "The external python file path is empty!" ) );
619  }
620  break;
621 
623  initCode = mLayer->editFormConfig()->initCode();
624  if ( initCode.isEmpty() )
625  {
626  QgsLogger::warning( QString( "The python code provided in the dialog is empty!" ) );
627  }
628  break;
629 
632  default:
633  // Nothing to do: the function code should be already in the environment
634  break;
635  }
636 
637  // If we have a function code, run it
638  if ( ! initCode.isEmpty() )
639  {
640  QgsPythonRunner::run( initCode );
641  }
642 
643  QgsPythonRunner::run( "import inspect" );
644  QString numArgs;
645 
646  // Check for eval result
647  if ( QgsPythonRunner::eval( QString( "len(inspect.getargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
648  {
649  static int sFormId = 0;
650  mPyFormVarName = QString( "_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
651 
652  QString form = QString( "%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
653  .arg( mPyFormVarName )
654  .arg(( unsigned long ) this );
655 
656  QgsPythonRunner::run( form );
657 
658  QgsDebugMsg( QString( "running featureForm init: %1" ).arg( mPyFormVarName ) );
659 
660  // Legacy
661  if ( numArgs == "3" )
662  {
663  addInterface( new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName, this ) );
664  }
665  else
666  {
667  // If we get here, it means that the function doesn't accept three arguments
668  QMessageBox msgBox;
669  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 ) );
670  msgBox.exec();
671 #if 0
672  QString expr = QString( "%1(%2)" )
673  .arg( mLayer->editFormInit() )
674  .arg( mPyFormVarName );
675  QgsAttributeFormInterface* iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface*>( expr, "QgsAttributeFormInterface" );
676  if ( iface )
677  addInterface( iface );
678 #endif
679  }
680  }
681  else
682  {
683  // If we get here, it means that inspect couldn't find the function
684  QMessageBox msgBox;
685  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 ) );
686  msgBox.exec();
687  }
688  }
689 }
690 
691 QWidget* QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement *widgetDef, QWidget *parent, QgsVectorLayer *vl, QgsAttributeEditorContext &context, QString &labelText, bool &labelOnTop )
692 {
693  QWidget *newWidget = nullptr;
694 
695  switch ( widgetDef->type() )
696  {
698  {
699  const QgsAttributeEditorField* fieldDef = dynamic_cast<const QgsAttributeEditorField*>( widgetDef );
700  if ( !fieldDef )
701  break;
702 
703  int fldIdx = vl->fieldNameIndex( fieldDef->name() );
704  if ( fldIdx < vl->fields().count() && fldIdx >= 0 )
705  {
706  const QString widgetType = mLayer->editFormConfig()->widgetType( fldIdx );
707  const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig()->widgetConfig( fldIdx );
708 
709  QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, fldIdx, widgetConfig, nullptr, this, mContext );
710  newWidget = eww->widget();
711  addWidgetWrapper( eww );
712 
713  newWidget->setObjectName( mLayer->fields().at( fldIdx ).name() );
714  }
715 
716  labelOnTop = mLayer->editFormConfig()->labelOnTop( fieldDef->idx() );
717  labelText = mLayer->attributeDisplayName( fieldDef->idx() );
718 
719  break;
720  }
721 
723  {
724  const QgsAttributeEditorRelation* relDef = dynamic_cast<const QgsAttributeEditorRelation*>( widgetDef );
725 
726  QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, relDef->relation(), nullptr, this );
727  QgsEditorWidgetConfig cfg = mLayer->editFormConfig()->widgetConfig( relDef->relation().id() );
728  rww->setConfig( cfg );
729  rww->setContext( context );
730  newWidget = rww->widget();
731  mWidgets.append( rww );
732  labelText = QString::null;
733  labelOnTop = true;
734  break;
735  }
736 
738  {
739  const QgsAttributeEditorContainer* container = dynamic_cast<const QgsAttributeEditorContainer*>( widgetDef );
740  if ( !container )
741  break;
742 
743  QWidget* myContainer;
744  if ( container->isGroupBox() )
745  {
746  QGroupBox* groupBox = new QGroupBox( parent );
747  groupBox->setTitle( container->name() );
748  myContainer = groupBox;
749  newWidget = myContainer;
750  }
751  else
752  {
753  QScrollArea *scrollArea = new QScrollArea( parent );
754 
755  myContainer = new QWidget( scrollArea );
756 
757  scrollArea->setWidget( myContainer );
758  scrollArea->setWidgetResizable( true );
759  scrollArea->setFrameShape( QFrame::NoFrame );
760 
761  newWidget = scrollArea;
762  }
763 
764  QGridLayout* gbLayout = new QGridLayout();
765  myContainer->setLayout( gbLayout );
766 
767  int index = 0;
768 
770 
771  Q_FOREACH ( QgsAttributeEditorElement* childDef, children )
772  {
773  QString labelText;
774  bool labelOnTop;
775  QWidget* editor = createWidgetFromDef( childDef, myContainer, vl, context, labelText, labelOnTop );
776 
777  if ( labelText.isNull() )
778  {
779  gbLayout->addWidget( editor, index, 0, 1, 2 );
780  }
781  else
782  {
783  QLabel* mypLabel = new QLabel( labelText );
784  if ( labelOnTop )
785  {
786  gbLayout->addWidget( mypLabel, index, 0, 1, 2 );
787  ++index;
788  gbLayout->addWidget( editor, index, 0, 1, 2 );
789  }
790  else
791  {
792  gbLayout->addWidget( mypLabel, index, 0 );
793  gbLayout->addWidget( editor, index, 1 );
794  }
795  }
796 
797  ++index;
798  }
799  QWidget* spacer = new QWidget();
800  spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
801  // gbLayout->addWidget( spacer, index, 0 );
802 
803  labelText = QString::null;
804  labelOnTop = true;
805  break;
806  }
807 
808  default:
809  QgsDebugMsg( "Unknown attribute editor widget type encountered..." );
810  break;
811  }
812 
813  return newWidget;
814 }
815 
816 void QgsAttributeForm::addWidgetWrapper( QgsEditorWidgetWrapper* eww )
817 {
818  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
819  {
820  QgsEditorWidgetWrapper* meww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
821  if ( meww )
822  {
823  if ( meww->field() == eww->field() )
824  {
825  connect( meww, SIGNAL( valueChanged( QVariant ) ), eww, SLOT( setValue( QVariant ) ) );
826  connect( eww, SIGNAL( valueChanged( QVariant ) ), meww, SLOT( setValue( QVariant ) ) );
827  break;
828  }
829  }
830  }
831 
832  mWidgets.append( eww );
833 }
834 
835 void QgsAttributeForm::createWrappers()
836 {
837  QList<QWidget*> myWidgets = findChildren<QWidget*>();
838  const QList<QgsField> fields = mLayer->fields().toList();
839 
840  Q_FOREACH ( QWidget* myWidget, myWidgets )
841  {
842  // Check the widget's properties for a relation definition
843  QVariant vRel = myWidget->property( "qgisRelation" );
844  if ( vRel.isValid() )
845  {
847  QgsRelation relation = relMgr->relation( vRel.toString() );
848  if ( relation.isValid() )
849  {
850  QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
851  rww->setConfig( mLayer->editFormConfig()->widgetConfig( relation.id() ) );
852  rww->setContext( mContext );
853  rww->widget(); // Will initialize the widget
854  mWidgets.append( rww );
855  }
856  }
857  else
858  {
859  Q_FOREACH ( const QgsField& field, fields )
860  {
861  if ( field.name() == myWidget->objectName() )
862  {
863  const QString widgetType = mLayer->editFormConfig()->widgetType( field.name() );
864  const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig()->widgetConfig( field.name() );
865  int idx = mLayer->fieldNameIndex( field.name() );
866 
867  QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, idx, widgetConfig, myWidget, this, mContext );
868  addWidgetWrapper( eww );
869  }
870  }
871  }
872  }
873 }
874 
875 void QgsAttributeForm::connectWrappers()
876 {
877  bool isFirstEww = true;
878 
879  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
880  {
881  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
882 
883  if ( eww )
884  {
885  if ( isFirstEww )
886  {
887  setFocusProxy( eww->widget() );
888  isFirstEww = false;
889  }
890 
891  connect( eww, SIGNAL( valueChanged( const QVariant& ) ), this, SLOT( onAttributeChanged( const QVariant& ) ) );
892  }
893  }
894 }
895 
896 
898 {
899  Q_UNUSED( object )
900 
901  if ( e->type() == QEvent::KeyPress )
902  {
903  QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>( e );
904  if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
905  {
906  // Re-emit to this form so it will be forwarded to parent
907  event( e );
908  return true;
909  }
910  }
911 
912  return false;
913 }
QLayout * layout() const
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:199
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 clear()
bool isValid() const
Returns the validity of this relation.
static unsigned index
Use the python code provided in the dialog.
void setWidget(QWidget *widget)
Type type() const
QgsAttributes attributes() const
Returns the feature&#39;s attributes.
Definition: qgsfeature.cpp:110
int size() const
Return number of items.
Definition: qgsfield.cpp:370
This is an abstract base class for any elements of a drag and drop form.
QgsField field() const
Access the field.
Q_DECL_DEPRECATED void accept()
Alias for save()
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
void addWidget(QWidget *widget, int row, int column, QFlags< Qt::AlignmentFlag > alignment)
void hideButtonBox()
Hides the button box (Ok/Cancel) and enables auto-commit.
#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.
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:124
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.
bool save()
Save all the values from the editors to the layer.
const QObjectList & children() const
Use a layout with tabs and group boxes. Needs to be configured.
void insert(int i, const T &value)
void setIsAddDialog(bool isAddDialog)
Toggles the form mode between edit feature and add feature.
bool isVisible() const
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.
This element will load a relation editor onto the form.
bool addFeature(QgsFeature &f, bool alsoUpdateExtent=true)
Adds a feature.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:187
QgsRelationManager * relationManager() const
void setWorkingDirectory(const QDir &dir)
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.
static QgsEditorWidgetRegistry * instance()
This class is a singleton and has therefore to be accessed with this method instead of a constructor...
QString tr(const char *sourceText, const char *disambiguation, int n)
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.
QString name() const
Gets the name of the field.
Definition: qgsfield.cpp:84
const QgsField & at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:385
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.
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 setConfig(const QgsEditorWidgetConfig &config)
Will set the config of this wrapper to the specified config.
void setEnabled(bool)
void append(const T &value)
const QgsFields * fields() const
Returns the field map associated with the feature.
Definition: qgsfeature.cpp:188
QVariant property(const char *name) const
void setLayout(QLayout *layout)
void installEventFilter(QObject *filterObj)
Do not use python code at all.
bool isNull() const
void setFeature(const QgsFeature &feature)
Update all editors to correspond to a different feature.
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 setObjectName(const QString &name)
void setFocusProxy(QWidget *w)
bool isEmpty() const
void setText(const QString &text)
void remove(int i)
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 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.
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()
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.
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.
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 featureSaved(const QgsFeature &feature)
Is emitted, when a feature is changed or added.
void setWidgetResizable(bool resizable)
virtual void close()
virtual bool acceptChanges(const QgsFeature &feature)
void setFrameShadow(Shadow)
void setValid(bool validity)
Sets the validity of the feature.
Definition: qgsfeature.cpp:204
QString initCode() const
Get python code for edit form initialization.
Q_DECL_DEPRECATED QString editFormInit() const
Get python function for edit form initialization.
void setWindowFlags(QFlags< Qt::WindowType > type)
const T & at(int i) 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:424
QgsEditorWidgetConfig widgetConfig(int fieldIdx) const
Get the configuration for the editor widget used to represent the field at the given index...
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.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a python statement.
void setTitle(const QString &title)
virtual void setIsGroupBox(bool isGroupBox)
Determines if this container is rendered as collapsible group box or tab in a tabwidget.
Load a .ui file for the layout. Needs to be configured.
This class manages a set of relations between layers.
void addItem(QLayoutItem *item, int row, int column, int rowSpan, int columnSpan, QFlags< Qt::AlignmentFlag > alignment)
static QgsProject * instance()
access to canonical QgsProject instance
Definition: qgsproject.cpp:381
QWidget(QWidget *parent, QFlags< Qt::WindowType > f)
virtual QVariant value() const =0
Will be used to access the widget&#39;s value.
QList< QgsField > toList() const
Utility function to return a list of QgsField instances.
Definition: qgsfield.cpp:429
int count(const T &value) const
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.
QList< QgsAttributeEditorElement * > children() const
Get a list of the children elements of this container.
bool isValid() const
QPushButton * button(StandardButton which) const
#define FID_IS_NEW(fid)
Definition: qgsfeature.h:87
const QgsFeature & feature()
void show()
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.
QgsVectorDataProvider * dataProvider()
Returns the data provider.
int fieldIdx() const
Access the field index.
bool nextFeature(QgsFeature &f)
bool labelOnTop(int idx) const
If this returns true, the widget at the given index will receive its label on the previous line while...
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:89
QString name() const
Return the name of this element.
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...
EditorLayout layout() const
Get the active layout style for the attribute editor for this layer.
Allows modification of attribute values.
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.
QString uiForm() const
Get path to the .ui form.
PythonInitCodeSource initCodeSource() const
Return python code source for edit form initialization (if it shall be loaded from a file...
#define tr(sourceText)
QString initFilePath() const
Get python external file path for edit form initialization.