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