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