QGIS API Documentation  2.17.0-Master (973e4b0)
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->hide();
784  mInvalidConstraintMessage->clear();
785  mInvalidConstraintMessage->setStyleSheet( QString() );
786 }
787 
788 void QgsAttributeForm::displayInvalidConstraintMessage( const QStringList& f,
789  const QStringList& d )
790 {
791  clearInvalidConstraintsMessage();
792 
793  // show only the third first errors (to avoid a too long label)
794  int max = 3;
795  int size = f.size() > max ? max : f.size();
796  QString descriptions;
797  for ( int i = 0; i < size; i++ )
798  descriptions += QString( "<li>%1: <i>%2</i></li>" ).arg( f[i] ).arg( d[i] );
799 
800  QString icPath = QgsApplication::iconPath( "/mIconWarn.png" );
801 
802  QString title = QString( "<img src=\"%1\"> <b>%2:" ).arg( icPath ).arg( tr( "Invalid fields" ) );
803  QString msg = QString( "%1</b><ul>%2</ul>" ).arg( title ).arg( descriptions ) ;
804 
805  mInvalidConstraintMessage->show();
806  mInvalidConstraintMessage->setText( msg );
807  mInvalidConstraintMessage->setStyleSheet( "QLabel { background-color : #ffc800; }" );
808 }
809 
810 bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields,
811  QStringList &descriptions )
812 {
813  bool valid( true );
814 
815  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
816  {
817  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
818  if ( eww )
819  {
820  if ( ! eww->isValidConstraint() )
821  {
822  invalidFields.append( eww->field().name() );
823 
824  QString desc = eww->layer()->editFormConfig()->expressionDescription( eww->fieldIdx() );
825  descriptions.append( desc );
826 
827  valid = false; // continue to get all invalif fields
828  }
829  }
830  }
831 
832  return valid;
833 }
834 
835 void QgsAttributeForm::onAttributeAdded( int idx )
836 {
837  mPreventFeatureRefresh = false;
838  if ( mFeature.isValid() )
839  {
840  QgsAttributes attrs = mFeature.attributes();
841  attrs.insert( idx, QVariant( layer()->fields().at( idx ).type() ) );
842  mFeature.setFields( layer()->fields() );
843  mFeature.setAttributes( attrs );
844  }
845  init();
846  setFeature( mFeature );
847 }
848 
849 void QgsAttributeForm::onAttributeDeleted( int idx )
850 {
851  mPreventFeatureRefresh = false;
852  if ( mFeature.isValid() )
853  {
854  QgsAttributes attrs = mFeature.attributes();
855  attrs.remove( idx );
856  mFeature.setFields( layer()->fields() );
857  mFeature.setAttributes( attrs );
858  }
859  init();
860  setFeature( mFeature );
861 }
862 
863 void QgsAttributeForm::onUpdatedFields()
864 {
865  mPreventFeatureRefresh = false;
866  if ( mFeature.isValid() )
867  {
868  QgsAttributes attrs( layer()->fields().size() );
869  for ( int i = 0; i < layer()->fields().size(); i++ )
870  {
871  int idx = mFeature.fields()->indexFromName( layer()->fields().at( i ).name() );
872  if ( idx != -1 )
873  {
874  attrs[i] = mFeature.attributes().at( idx );
875  if ( mFeature.attributes().at( idx ).type() != layer()->fields().at( i ).type() )
876  {
877  attrs[i].convert( layer()->fields().at( i ).type() );
878  }
879  }
880  else
881  {
882  attrs[i] = QVariant( layer()->fields().at( i ).type() );
883  }
884  }
885  mFeature.setFields( layer()->fields() );
886  mFeature.setAttributes( attrs );
887  }
888  init();
889  setFeature( mFeature );
890 }
891 
892 void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint,
893  const QString& description, const QString& err, bool ok )
894 {
895  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( sender() );
896  Q_ASSERT( eww );
897 
898  QLabel* buddy = mBuddyMap.value( eww->widget() );
899 
900  if ( buddy )
901  {
902  QString tooltip = tr( "Description: " ) + description + "\n" +
903  tr( "Raw expression: " ) + constraint + "\n" + tr( "Constraint: " ) + err;
904  buddy->setToolTip( tooltip );
905 
906  if ( !buddy->property( "originalText" ).isValid() )
907  buddy->setProperty( "originalText", buddy->text() );
908 
909  QString text = buddy->property( "originalText" ).toString();
910 
911  if ( !ok )
912  {
913  // not good
914  buddy->setText( QString( "%1<font color=\"red\">*</font>" ).arg( text ) );
915  }
916  else
917  {
918  // good
919  buddy->setText( QString( "%1<font color=\"green\">*</font>" ).arg( text ) );
920  }
921  }
922 }
923 
924 void QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper* w,
926 {
927  QString name = w->field().name();
928 
929  // for each widget in the current form
930  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
931  {
932  // get the wrapper
933  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
934  if ( eww )
935  {
936  // compare name to not compare w to itself
937  QString ewwName = eww->field().name();
938  if ( name != ewwName )
939  {
940  // get expression and referencedColumns
941  QgsExpression expr = eww->layer()->editFormConfig()->expression( eww->fieldIdx() );
942 
943  Q_FOREACH ( const QString& colName, expr.referencedColumns() )
944  {
945  if ( name == colName )
946  {
947  wDeps.append( eww );
948  break;
949  }
950  }
951  }
952  }
953  }
954 }
955 
956 void QgsAttributeForm::preventFeatureRefresh()
957 {
958  mPreventFeatureRefresh = true;
959 }
960 
962 {
963  if ( mPreventFeatureRefresh || mLayer->isEditable() || !mFeature.isValid() )
964  return;
965 
966  // reload feature if layer changed although not editable
967  // (datasource probably changed bypassing QgsVectorLayer)
968  if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeature.id() ) ).nextFeature( mFeature ) )
969  return;
970 
971  init();
972  setFeature( mFeature );
973 }
974 
975 void QgsAttributeForm::synchronizeEnabledState()
976 {
977  bool isEditable = ( mFeature.isValid()
978  || mMode == AddFeatureMode
979  || mMode == MultiEditMode ) && mLayer->isEditable();
980 
981  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
982  {
983  bool fieldEditable = true;
984  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
985  if ( eww )
986  {
987  fieldEditable = !mLayer->editFormConfig()->readOnly( eww->fieldIdx() ) &&
989  FID_IS_NEW( mFeature.id() ) );
990  }
991  ww->setEnabled( isEditable && fieldEditable );
992  }
993 
994  // push a message and disable the OK button if constraints are invalid
995  clearInvalidConstraintsMessage();
996 
997  if ( mMode != SearchMode )
998  {
999  QStringList invalidFields, descriptions;
1000  bool validConstraint = currentFormValidConstraints( invalidFields, descriptions );
1001 
1002  if ( ! validConstraint )
1003  displayInvalidConstraintMessage( invalidFields, descriptions );
1004 
1005  isEditable = isEditable & validConstraint;
1006  }
1007 
1008  // change ok button status
1009  QPushButton* okButton = mButtonBox->button( QDialogButtonBox::Ok );
1010  if ( okButton )
1011  okButton->setEnabled( isEditable );
1012 }
1013 
1014 void QgsAttributeForm::init()
1015 {
1016  QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1017 
1018  // Cleanup of any previously shown widget, we start from scratch
1019  QWidget* formWidget = nullptr;
1020 
1021  bool buttonBoxVisible = true;
1022  // Cleanup button box but preserve visibility
1023  if ( mButtonBox )
1024  {
1025  buttonBoxVisible = mButtonBox->isVisible();
1026  delete mButtonBox;
1027  mButtonBox = nullptr;
1028  }
1029 
1030  if ( mSearchButtonBox )
1031  {
1032  delete mSearchButtonBox;
1033  mSearchButtonBox = nullptr;
1034  }
1035 
1036  qDeleteAll( mWidgets );
1037  mWidgets.clear();
1038 
1039  while ( QWidget* w = this->findChild<QWidget*>() )
1040  {
1041  delete w;
1042  }
1043  delete layout();
1044 
1045  QVBoxLayout* vl = new QVBoxLayout();
1046  vl->setMargin( 0 );
1047  vl->setContentsMargins( 0, 0, 0, 0 );
1048  mMessageBar = new QgsMessageBar( this );
1049  mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1050  vl->addWidget( mMessageBar );
1051 
1052  mInvalidConstraintMessage = new QLabel( this );
1053  mInvalidConstraintMessage->hide();
1054  vl->addWidget( mInvalidConstraintMessage );
1055 
1056  setLayout( vl );
1057 
1058  // Get a layout
1059  QGridLayout* layout = new QGridLayout();
1060  QWidget* container = new QWidget();
1061  container->setLayout( layout );
1062  vl->addWidget( container );
1063 
1064  mFormEditorWidgets.clear();
1065 
1066  // a bar to warn the user with non-blocking messages
1067  setContentsMargins( 0, 0, 0, 0 );
1068 
1069  // Try to load Ui-File for layout
1070  if ( mContext.allowCustomUi() && mLayer->editFormConfig()->layout() == QgsEditFormConfig::UiFileLayout &&
1071  !mLayer->editFormConfig()->uiForm().isEmpty() )
1072  {
1073  QFile file( mLayer->editFormConfig()->uiForm() );
1074 
1075  if ( file.open( QFile::ReadOnly ) )
1076  {
1077  QUiLoader loader;
1078 
1079  QFileInfo fi( mLayer->editFormConfig()->uiForm() );
1080  loader.setWorkingDirectory( fi.dir() );
1081  formWidget = loader.load( &file, this );
1082  formWidget->setWindowFlags( Qt::Widget );
1083  layout->addWidget( formWidget );
1084  formWidget->show();
1085  file.close();
1086  mButtonBox = findChild<QDialogButtonBox*>();
1087  createWrappers();
1088 
1089  formWidget->installEventFilter( this );
1090  }
1091  }
1092 
1093  QTabWidget* tabWidget = nullptr;
1094 
1095  // Tab layout
1096  if ( !formWidget && mLayer->editFormConfig()->layout() == QgsEditFormConfig::TabLayout )
1097  {
1098  int row = 0;
1099  int column = 0;
1100  int columnCount = 1;
1101 
1102  Q_FOREACH ( QgsAttributeEditorElement* widgDef, mLayer->editFormConfig()->tabs() )
1103  {
1105  {
1106  QgsAttributeEditorContainer* containerDef = dynamic_cast<QgsAttributeEditorContainer*>( widgDef );
1107  if ( !containerDef )
1108  continue;
1109 
1110  if ( containerDef->isGroupBox() )
1111  {
1112  tabWidget = nullptr;
1113  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1114  layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1115  column += 2;
1116  }
1117  else
1118  {
1119  if ( !tabWidget )
1120  {
1121  tabWidget = new QTabWidget();
1122  layout->addWidget( tabWidget, row, column, 1, 2 );
1123  column += 2;
1124  }
1125 
1126  QWidget* tabPage = new QWidget( tabWidget );
1127 
1128  tabWidget->addTab( tabPage, widgDef->name() );
1129  QGridLayout* tabPageLayout = new QGridLayout();
1130  tabPage->setLayout( tabPageLayout );
1131 
1132  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1133  tabPageLayout->addWidget( widgetInfo.widget );
1134  }
1135  }
1136  else
1137  {
1138  tabWidget = nullptr;
1139  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1140  QLabel* label = new QLabel( widgetInfo.labelText );
1141  if ( columnCount > 1 && !widgetInfo.labelOnTop )
1142  {
1143  label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1144  }
1145 
1146  label->setBuddy( widgetInfo.widget );
1147 
1148  if ( !widgetInfo.showLabel )
1149  {
1150  QVBoxLayout* c = new QVBoxLayout();
1151  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1152  c->addWidget( widgetInfo.widget );
1153  layout->addLayout( c, row, column, 1, 2 );
1154  column += 2;
1155  }
1156  else if ( widgetInfo.labelOnTop )
1157  {
1158  QVBoxLayout* c = new QVBoxLayout();
1159  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1160  c->addWidget( label );
1161  c->addWidget( widgetInfo.widget );
1162  layout->addLayout( c, row, column, 1, 2 );
1163  column += 2;
1164  }
1165  else
1166  {
1167  layout->addWidget( label, row, column++ );
1168  layout->addWidget( widgetInfo.widget, row, column++ );
1169  }
1170  }
1171 
1172  if ( column >= columnCount * 2 )
1173  {
1174  column = 0;
1175  row += 1;
1176  }
1177  }
1178  formWidget = container;
1179  }
1180 
1181  // Autogenerate Layout
1182  // If there is still no layout loaded (defined as autogenerate or other methods failed)
1183  if ( !formWidget )
1184  {
1185  formWidget = new QWidget( this );
1186  QGridLayout* gridLayout = new QGridLayout( formWidget );
1187  formWidget->setLayout( gridLayout );
1188 
1189  if ( mContext.formMode() != QgsAttributeEditorContext::Embed )
1190  {
1191  // put the form into a scroll area to nicely handle cases with lots of attributes
1192  QScrollArea* scrollArea = new QScrollArea( this );
1193  scrollArea->setWidget( formWidget );
1194  scrollArea->setWidgetResizable( true );
1195  scrollArea->setFrameShape( QFrame::NoFrame );
1196  scrollArea->setFrameShadow( QFrame::Plain );
1197  scrollArea->setFocusProxy( this );
1198  layout->addWidget( scrollArea );
1199  }
1200  else
1201  {
1202  layout->addWidget( formWidget );
1203  }
1204 
1205  int row = 0;
1206  Q_FOREACH ( const QgsField& field, mLayer->fields().toList() )
1207  {
1208  int idx = mLayer->fieldNameIndex( field.name() );
1209  if ( idx < 0 )
1210  continue;
1211 
1212  //show attribute alias if available
1213  QString fieldName = mLayer->attributeDisplayName( idx );
1214 
1215  const QString widgetType = mLayer->editFormConfig()->widgetType( idx );
1216 
1217  if ( widgetType == "Hidden" )
1218  continue;
1219 
1220  const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig()->widgetConfig( idx );
1221  bool labelOnTop = mLayer->editFormConfig()->labelOnTop( idx );
1222 
1223  // This will also create the widget
1224  QLabel *l = new QLabel( fieldName );
1225  QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, idx, widgetConfig, nullptr, this, mContext );
1226 
1227  QWidget* w = nullptr;
1228  if ( eww )
1229  {
1230  QgsAttributeFormEditorWidget* formWidget = new QgsAttributeFormEditorWidget( eww, this );
1231  w = formWidget;
1232  mFormEditorWidgets.insert( idx, formWidget );
1233  formWidget->createSearchWidgetWrappers( widgetType, idx, widgetConfig, mContext );
1234 
1235  l->setBuddy( eww->widget() );
1236  }
1237  else
1238  {
1239  w = new QLabel( QString( "<p style=\"color: red; font-style: italic;\">Failed to create widget with type '%1'</p>" ).arg( widgetType ) );
1240  }
1241 
1242 
1243  if ( w )
1244  w->setObjectName( field.name() );
1245 
1246  if ( eww )
1247  addWidgetWrapper( eww );
1248 
1249  if ( labelOnTop )
1250  {
1251  gridLayout->addWidget( l, row++, 0, 1, 2 );
1252  gridLayout->addWidget( w, row++, 0, 1, 2 );
1253  }
1254  else
1255  {
1256  gridLayout->addWidget( l, row, 0 );
1257  gridLayout->addWidget( w, row++, 1 );
1258  }
1259  }
1260 
1261  Q_FOREACH ( const QgsRelation& rel, QgsProject::instance()->relationManager()->referencedRelations( mLayer ) )
1262  {
1263  QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, rel, nullptr, this );
1264  QgsEditorWidgetConfig cfg = mLayer->editFormConfig()->widgetConfig( rel.id() );
1265  rww->setConfig( cfg );
1266  rww->setContext( mContext );
1267  gridLayout->addWidget( rww->widget(), row++, 0, 1, 2 );
1268  mWidgets.append( rww );
1269  }
1270 
1271  if ( QgsProject::instance()->relationManager()->referencedRelations( mLayer ).isEmpty() )
1272  {
1273  QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1274  gridLayout->addItem( spacerItem, row, 0 );
1275  gridLayout->setRowStretch( row, 1 );
1276  row++;
1277  }
1278  }
1279 
1280  if ( !mButtonBox )
1281  {
1282  mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1283  mButtonBox->setObjectName( "buttonBox" );
1284  layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
1285  }
1286  mButtonBox->setVisible( buttonBoxVisible );
1287 
1288  if ( !mSearchButtonBox )
1289  {
1290  mSearchButtonBox = new QWidget();
1291  QHBoxLayout* boxLayout = new QHBoxLayout();
1292  boxLayout->setMargin( 0 );
1293  boxLayout->setContentsMargins( 0, 0, 0, 0 );
1294  mSearchButtonBox->setLayout( boxLayout );
1295  mSearchButtonBox->setObjectName( "searchButtonBox" );
1296 
1297  QPushButton* clearButton = new QPushButton( tr( "&Reset form" ), mSearchButtonBox );
1298  connect( clearButton, SIGNAL( clicked( bool ) ), this, SLOT( resetSearch() ) );
1299  boxLayout->addWidget( clearButton );
1300  boxLayout->addStretch( 1 );
1301 
1302  QToolButton* selectButton = new QToolButton();
1303  selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1304  selectButton->setText( tr( "&Select features" ) );
1305  selectButton->setPopupMode( QToolButton::MenuButtonPopup );
1306  connect( selectButton, SIGNAL( clicked( bool ) ), this, SLOT( searchSetSelection() ) );
1307  QMenu* selectMenu = new QMenu( selectButton );
1308  QAction* selectAction = new QAction( tr( "Select features" ), selectMenu );
1309  connect( selectAction, SIGNAL( triggered( bool ) ), this, SLOT( searchSetSelection() ) );
1310  selectMenu->addAction( selectAction );
1311  QAction* addSelectAction = new QAction( tr( "Add to current selection" ), selectMenu );
1312  connect( addSelectAction, SIGNAL( triggered( bool ) ), this, SLOT( searchAddToSelection() ) );
1313  selectMenu->addAction( addSelectAction );
1314  QAction* filterSelectAction = new QAction( tr( "Filter current selection" ), selectMenu );
1315  connect( filterSelectAction, SIGNAL( triggered( bool ) ), this, SLOT( searchIntersectSelection() ) );
1316  selectMenu->addAction( filterSelectAction );
1317  QAction* deselectAction = new QAction( tr( "Remove from current selection" ), selectMenu );
1318  connect( deselectAction, SIGNAL( triggered( bool ) ), this, SLOT( searchRemoveFromSelection() ) );
1319  selectMenu->addAction( deselectAction );
1320  selectButton->setMenu( selectMenu );
1321  boxLayout->addWidget( selectButton );
1322 
1323  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
1324  {
1325  QToolButton* filterButton = new QToolButton();
1326  filterButton->setText( tr( "Filter features" ) );
1327  filterButton->setPopupMode( QToolButton::MenuButtonPopup );
1328  filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1329  connect( filterButton, SIGNAL( clicked( bool ) ), this, SLOT( filterTriggered() ) );
1330  QMenu* filterMenu = new QMenu( filterButton );
1331  QAction* filterAndAction = new QAction( tr( "Filter within (\"AND\")" ), filterMenu );
1332  connect( filterAndAction, SIGNAL( triggered( bool ) ), this, SLOT( filterAndTriggered() ) );
1333  filterMenu->addAction( filterAndAction );
1334  QAction* filterOrAction = new QAction( tr( "Extend filter (\"OR\")" ), filterMenu );
1335  connect( filterOrAction, SIGNAL( triggered( bool ) ), this, SLOT( filterOrTriggered() ) );
1336  filterMenu->addAction( filterOrAction );
1337  filterButton->setMenu( filterMenu );
1338  boxLayout->addWidget( filterButton );
1339  }
1340  else
1341  {
1342  QPushButton* closeButton = new QPushButton( tr( "Close" ), mSearchButtonBox );
1343  connect( closeButton, SIGNAL( clicked( bool ) ), this, SIGNAL( closed() ) );
1344  closeButton->setShortcut( Qt::Key_Escape );
1345  boxLayout->addWidget( closeButton );
1346  }
1347 
1348  layout->addWidget( mSearchButtonBox );
1349  }
1350  mSearchButtonBox->setVisible( mMode == SearchMode );
1351 
1352  afterWidgetInit();
1353 
1354  connect( mButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
1355  connect( mButtonBox, SIGNAL( rejected() ), this, SLOT( resetValues() ) );
1356 
1357  connect( mLayer, SIGNAL( editingStarted() ), this, SLOT( synchronizeEnabledState() ) );
1358  connect( mLayer, SIGNAL( editingStopped() ), this, SLOT( synchronizeEnabledState() ) );
1359 
1360  Q_FOREACH ( QgsAttributeFormInterface* iface, mInterfaces )
1361  {
1362  iface->initForm();
1363  }
1364 
1365  if ( mContext.formMode() == QgsAttributeEditorContext::Embed || mMode == SearchMode )
1366  {
1367  hideButtonBox();
1368  }
1369 
1371 }
1372 
1373 void QgsAttributeForm::cleanPython()
1374 {
1375  if ( !mPyFormVarName.isNull() )
1376  {
1377  QString expr = QString( "if locals().has_key('%1'): del %1\n" ).arg( mPyFormVarName );
1378  QgsPythonRunner::run( expr );
1379  }
1380 }
1381 
1382 void QgsAttributeForm::initPython()
1383 {
1384  cleanPython();
1385 
1386  // Init Python, if init function is not empty and the combo indicates
1387  // the source for the function code
1388  if ( !mLayer->editFormConfig()->initFunction().isEmpty()
1390  {
1391 
1392  QString initFunction = mLayer->editFormConfig()->initFunction();
1393  QString initFilePath = mLayer->editFormConfig()->initFilePath();
1394  QString initCode;
1395 
1396  switch ( mLayer->editFormConfig()->initCodeSource() )
1397  {
1399  if ( ! initFilePath.isEmpty() )
1400  {
1401  QFile inputFile( initFilePath );
1402 
1403  if ( inputFile.open( QFile::ReadOnly ) )
1404  {
1405  // Read it into a string
1406  QTextStream inf( &inputFile );
1407  initCode = inf.readAll();
1408  inputFile.close();
1409  }
1410  else // The file couldn't be opened
1411  {
1412  QgsLogger::warning( QString( "The external python file path %1 could not be opened!" ).arg( initFilePath ) );
1413  }
1414  }
1415  else
1416  {
1417  QgsLogger::warning( QString( "The external python file path is empty!" ) );
1418  }
1419  break;
1420 
1422  initCode = mLayer->editFormConfig()->initCode();
1423  if ( initCode.isEmpty() )
1424  {
1425  QgsLogger::warning( QString( "The python code provided in the dialog is empty!" ) );
1426  }
1427  break;
1428 
1431  default:
1432  // Nothing to do: the function code should be already in the environment
1433  break;
1434  }
1435 
1436  // If we have a function code, run it
1437  if ( ! initCode.isEmpty() )
1438  {
1439  QgsPythonRunner::run( initCode );
1440  }
1441 
1442  QgsPythonRunner::run( "import inspect" );
1443  QString numArgs;
1444 
1445  // Check for eval result
1446  if ( QgsPythonRunner::eval( QString( "len(inspect.getargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
1447  {
1448  static int sFormId = 0;
1449  mPyFormVarName = QString( "_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
1450 
1451  QString form = QString( "%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
1452  .arg( mPyFormVarName )
1453  .arg(( unsigned long ) this );
1454 
1455  QgsPythonRunner::run( form );
1456 
1457  QgsDebugMsg( QString( "running featureForm init: %1" ).arg( mPyFormVarName ) );
1458 
1459  // Legacy
1460  if ( numArgs == "3" )
1461  {
1462  addInterface( new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName, this ) );
1463  }
1464  else
1465  {
1466  // If we get here, it means that the function doesn't accept three arguments
1467  QMessageBox msgBox;
1468  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 ) );
1469  msgBox.exec();
1470 #if 0
1471  QString expr = QString( "%1(%2)" )
1472  .arg( mLayer->editFormInit() )
1473  .arg( mPyFormVarName );
1474  QgsAttributeFormInterface* iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface*>( expr, "QgsAttributeFormInterface" );
1475  if ( iface )
1476  addInterface( iface );
1477 #endif
1478  }
1479  }
1480  else
1481  {
1482  // If we get here, it means that inspect couldn't find the function
1483  QMessageBox msgBox;
1484  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 ) );
1485  msgBox.exec();
1486  }
1487  }
1488 }
1489 
1490 QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement* widgetDef, QWidget* parent, QgsVectorLayer* vl, QgsAttributeEditorContext& context )
1491 {
1492  WidgetInfo newWidgetInfo;
1493 
1494  switch ( widgetDef->type() )
1495  {
1497  {
1498  const QgsAttributeEditorField* fieldDef = dynamic_cast<const QgsAttributeEditorField*>( widgetDef );
1499  if ( !fieldDef )
1500  break;
1501 
1502  int fldIdx = vl->fieldNameIndex( fieldDef->name() );
1503  if ( fldIdx < vl->fields().count() && fldIdx >= 0 )
1504  {
1505  const QString widgetType = mLayer->editFormConfig()->widgetType( fldIdx );
1506  const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig()->widgetConfig( fldIdx );
1507 
1508  QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, fldIdx, widgetConfig, nullptr, this, mContext );
1510  mFormEditorWidgets.insert( fldIdx, w );
1511 
1512  w->createSearchWidgetWrappers( widgetType, fldIdx, widgetConfig, mContext );
1513 
1514  newWidgetInfo.widget = w;
1515  addWidgetWrapper( eww );
1516 
1517  newWidgetInfo.widget->setObjectName( mLayer->fields().at( fldIdx ).name() );
1518  }
1519 
1520  newWidgetInfo.labelOnTop = mLayer->editFormConfig()->labelOnTop( fieldDef->idx() );
1521  newWidgetInfo.labelText = mLayer->attributeDisplayName( fieldDef->idx() );
1522  newWidgetInfo.showLabel = widgetDef->showLabel();
1523 
1524  break;
1525  }
1526 
1528  {
1529  const QgsAttributeEditorRelation* relDef = dynamic_cast<const QgsAttributeEditorRelation*>( widgetDef );
1530 
1531  QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, relDef->relation(), nullptr, this );
1532  QgsEditorWidgetConfig cfg = mLayer->editFormConfig()->widgetConfig( relDef->relation().id() );
1533  rww->setConfig( cfg );
1534  rww->setContext( context );
1535  newWidgetInfo.widget = rww->widget();
1536  rww->setShowLabel( relDef->showLabel() );
1537  mWidgets.append( rww );
1538  newWidgetInfo.labelText = QString::null;
1539  newWidgetInfo.labelOnTop = true;
1540  break;
1541  }
1542 
1544  {
1545  const QgsAttributeEditorContainer* container = dynamic_cast<const QgsAttributeEditorContainer*>( widgetDef );
1546  if ( !container )
1547  break;
1548 
1549  int columnCount = container->columnCount();
1550 
1551  if ( columnCount <= 0 )
1552  columnCount = 1;
1553 
1554  QWidget* myContainer;
1555  if ( container->isGroupBox() )
1556  {
1557  QGroupBox* groupBox = new QGroupBox( parent );
1558  if ( container->showLabel() )
1559  groupBox->setTitle( container->name() );
1560  myContainer = groupBox;
1561  newWidgetInfo.widget = myContainer;
1562  }
1563  else
1564  {
1565  myContainer = new QWidget();
1566 
1567  if ( context.formMode() != QgsAttributeEditorContext::Embed )
1568  {
1569  QScrollArea *scrollArea = new QScrollArea( parent );
1570 
1571  scrollArea->setWidget( myContainer );
1572  scrollArea->setWidgetResizable( true );
1573  scrollArea->setFrameShape( QFrame::NoFrame );
1574 
1575  newWidgetInfo.widget = scrollArea;
1576  }
1577  else
1578  {
1579  newWidgetInfo.widget = myContainer;
1580  }
1581  }
1582 
1583  QGridLayout* gbLayout = new QGridLayout();
1584  myContainer->setLayout( gbLayout );
1585 
1586  int row = 0;
1587  int column = 0;
1588 
1590 
1591  Q_FOREACH ( QgsAttributeEditorElement* childDef, children )
1592  {
1593  WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
1594 
1595  if ( widgetInfo.labelText.isNull() )
1596  {
1597  gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1598  column += 2;
1599  }
1600  else
1601  {
1602  QLabel* mypLabel = new QLabel( widgetInfo.labelText );
1603  if ( columnCount > 1 && !widgetInfo.labelOnTop )
1604  {
1605  mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1606  }
1607 
1608  mypLabel->setBuddy( widgetInfo.widget );
1609 
1610  if ( widgetInfo.labelOnTop )
1611  {
1612  QVBoxLayout* c = new QVBoxLayout();
1613  mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1614  c->layout()->addWidget( mypLabel );
1615  c->layout()->addWidget( widgetInfo.widget );
1616  gbLayout->addLayout( c, row, column, 1, 2 );
1617  column += 2;
1618  }
1619  else
1620  {
1621  gbLayout->addWidget( mypLabel, row, column++ );
1622  gbLayout->addWidget( widgetInfo.widget, row, column++ );
1623  }
1624  }
1625 
1626  if ( column >= columnCount * 2 )
1627  {
1628  column = 0;
1629  row += 1;
1630  }
1631  }
1632  QWidget* spacer = new QWidget();
1633  spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
1634  gbLayout->addWidget( spacer, ++row, 0 );
1635  gbLayout->setRowStretch( row, 1 );
1636 
1637  newWidgetInfo.labelText = QString::null;
1638  newWidgetInfo.labelOnTop = true;
1639  break;
1640  }
1641 
1642  default:
1643  QgsDebugMsg( "Unknown attribute editor widget type encountered..." );
1644  break;
1645  }
1646 
1647  newWidgetInfo.showLabel = widgetDef->showLabel();
1648 
1649  return newWidgetInfo;
1650 }
1651 
1652 void QgsAttributeForm::addWidgetWrapper( QgsEditorWidgetWrapper* eww )
1653 {
1654  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
1655  {
1656  QgsEditorWidgetWrapper* meww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
1657  if ( meww )
1658  {
1659  if ( meww->field() == eww->field() )
1660  {
1661  connect( meww, SIGNAL( valueChanged( QVariant ) ), eww, SLOT( setValue( QVariant ) ) );
1662  connect( eww, SIGNAL( valueChanged( QVariant ) ), meww, SLOT( setValue( QVariant ) ) );
1663  break;
1664  }
1665  }
1666  }
1667 
1668  mWidgets.append( eww );
1669 }
1670 
1671 void QgsAttributeForm::createWrappers()
1672 {
1673  QList<QWidget*> myWidgets = findChildren<QWidget*>();
1674  const QList<QgsField> fields = mLayer->fields().toList();
1675 
1676  Q_FOREACH ( QWidget* myWidget, myWidgets )
1677  {
1678  // Check the widget's properties for a relation definition
1679  QVariant vRel = myWidget->property( "qgisRelation" );
1680  if ( vRel.isValid() )
1681  {
1683  QgsRelation relation = relMgr->relation( vRel.toString() );
1684  if ( relation.isValid() )
1685  {
1686  QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
1687  rww->setConfig( mLayer->editFormConfig()->widgetConfig( relation.id() ) );
1688  rww->setContext( mContext );
1689  rww->widget(); // Will initialize the widget
1690  mWidgets.append( rww );
1691  }
1692  }
1693  else
1694  {
1695  Q_FOREACH ( const QgsField& field, fields )
1696  {
1697  if ( field.name() == myWidget->objectName() )
1698  {
1699  const QString widgetType = mLayer->editFormConfig()->widgetType( field.name() );
1700  const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig()->widgetConfig( field.name() );
1701  int idx = mLayer->fieldNameIndex( field.name() );
1702 
1703  QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, idx, widgetConfig, myWidget, this, mContext );
1704  addWidgetWrapper( eww );
1705  }
1706  }
1707  }
1708  }
1709 }
1710 
1711 void QgsAttributeForm::afterWidgetInit()
1712 {
1713  bool isFirstEww = true;
1714 
1715  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
1716  {
1717  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
1718 
1719  if ( eww )
1720  {
1721  if ( isFirstEww )
1722  {
1723  setFocusProxy( eww->widget() );
1724  isFirstEww = false;
1725  }
1726 
1727  connect( eww, SIGNAL( valueChanged( const QVariant& ) ), this, SLOT( onAttributeChanged( const QVariant& ) ) );
1728  connect( eww, SIGNAL( constraintStatusChanged( QString, QString, QString, bool ) ),
1729  this, SLOT( onConstraintStatusChanged( QString, QString, QString, bool ) ) );
1730  }
1731  }
1732 
1733  // Update buddy widget list
1734  mBuddyMap.clear();
1735  QList<QLabel*> labels = findChildren<QLabel*>();
1736 
1737  Q_FOREACH ( QLabel* label, labels )
1738  {
1739  if ( label->buddy() )
1740  mBuddyMap.insert( label->buddy(), label );
1741  }
1742 }
1743 
1744 
1746 {
1747  Q_UNUSED( object )
1748 
1749  if ( e->type() == QEvent::KeyPress )
1750  {
1751  QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>( e );
1752  if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
1753  {
1754  // Re-emit to this form so it will be forwarded to parent
1755  event( e );
1756  return true;
1757  }
1758  }
1759 
1760  return false;
1761 }
1762 
1763 void QgsAttributeForm::scanForEqualAttributes( QgsFeatureIterator& fit, QSet< int >& mixedValueFields, QHash< int, QVariant >& fieldSharedValues ) const
1764 {
1765  mixedValueFields.clear();
1766  fieldSharedValues.clear();
1767 
1768  QgsFeature f;
1769  bool first = true;
1770  while ( fit.nextFeature( f ) )
1771  {
1772  for ( int i = 0; i < mLayer->fields().count(); ++i )
1773  {
1774  if ( mixedValueFields.contains( i ) )
1775  continue;
1776 
1777  if ( first )
1778  {
1779  fieldSharedValues[i] = f.attribute( i );
1780  }
1781  else
1782  {
1783  if ( fieldSharedValues.value( i ) != f.attribute( i ) )
1784  {
1785  fieldSharedValues.remove( i );
1786  mixedValueFields.insert( i );
1787  }
1788  }
1789  }
1790  first = false;
1791 
1792  if ( mixedValueFields.count() == mLayer->fields().count() )
1793  {
1794  // all attributes are mixed, no need to keep scanning
1795  break;
1796  }
1797  }
1798 }
1799 
1800 
1801 void QgsAttributeForm::layerSelectionChanged()
1802 {
1803  switch ( mMode )
1804  {
1805  case SingleEditMode:
1806  case AddFeatureMode:
1807  case SearchMode:
1808  break;
1809 
1810  case MultiEditMode:
1811  resetMultiEdit( true );
1812  break;
1813  }
1814 }
1815 
1817 {
1818  mIsSettingMultiEditFeatures = true;
1819  mMultiEditFeatureIds = fids;
1820 
1821  if ( fids.isEmpty() )
1822  {
1823  // no selected features
1825  for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
1826  {
1827  wIt.value()->initialize( QVariant() );
1828  }
1829  mIsSettingMultiEditFeatures = false;
1830  return;
1831  }
1832 
1833  QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFids( fids ) );
1834 
1835  // Scan through all features to determine which attributes are initially the same
1836  QSet< int > mixedValueFields;
1837  QHash< int, QVariant > fieldSharedValues;
1838  scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
1839 
1840  // also fetch just first feature
1841  fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFid( *fids.constBegin() ) );
1842  QgsFeature firstFeature;
1843  fit.nextFeature( firstFeature );
1844 
1845  Q_FOREACH ( int field, mixedValueFields )
1846  {
1847  if ( QgsAttributeFormEditorWidget* w = mFormEditorWidgets.value( field, nullptr ) )
1848  {
1849  w->initialize( firstFeature.attribute( field ), true );
1850  }
1851  }
1852  QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
1853  for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
1854  {
1855  if ( QgsAttributeFormEditorWidget* w = mFormEditorWidgets.value( sharedValueIt.key(), nullptr ) )
1856  {
1857  w->initialize( sharedValueIt.value(), false );
1858  }
1859  }
1860  mIsSettingMultiEditFeatures = false;
1861 }
1862 
1864 {
1865  if ( mOwnsMessageBar )
1866  delete mMessageBar;
1867  mOwnsMessageBar = false;
1868  mMessageBar = messageBar;
1869 }
1870 
1871 int QgsAttributeForm::messageTimeout()
1872 {
1873  QSettings settings;
1874  return settings.value( "/qgis/messageTimeout", 5 ).toInt();
1875 }
QLayout * layout() const
QList< QgsField > toList() const
Utility function to return a list of QgsField instances.
Definition: qgsfield.cpp:434
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.
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.
QString name
Definition: qgsfield.h:52
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.
QWidget * widget() const
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.
void setShowLabel(bool showLabel)
Defines if a title lable should be shown for this widget.
This element will load a relation editor onto the form.
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 pushMessage(const QString &text, MessageLevel level=INFO, int duration=5)
convenience method for pushing a message to the bar
Definition: qgsmessagebar.h:90
void setConfig(const QgsEditorWidgetConfig &config)
Will set the config of this wrapper to the specified config.
void setEnabled(bool)
void addWidget(QWidget *widget, int stretch, QFlags< Qt::AlignmentFlag > alignment)
QVariantMap QgsEditorWidgetConfig
Holds a set of configuration parameters for a editor widget wrapper.
void append(const T &value)
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.
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:370
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:390
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:429
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.
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:375
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 showLabel() const
Controls if this element should be labeled with a title (field, relation or groupname).
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.