QGIS API Documentation  2.17.0-Master (0497e4a)
qgsattributeform.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsattributeform.cpp
3  --------------------------------------
4  Date : 3.5.2014
5  Copyright : (C) 2014 Matthias Kuhn
6  Email : matthias at opengis dot ch
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgsattributeform.h"
17 
18 #include "qgsattributeeditor.h"
22 #include "qgsproject.h"
23 #include "qgspythonrunner.h"
25 #include "qgsvectordataprovider.h"
27 #include "qgsmessagebar.h"
28 #include "qgsmessagebaritem.h"
29 #include "qgstabwidget.h"
30 
31 #include <QDir>
32 #include <QTextStream>
33 #include <QFileInfo>
34 #include <QFile>
35 #include <QFormLayout>
36 #include <QGridLayout>
37 #include <QGroupBox>
38 #include <QKeyEvent>
39 #include <QLabel>
40 #include <QPushButton>
41 #include <QScrollArea>
42 #include <QUiLoader>
43 #include <QMessageBox>
44 #include <QSettings>
45 #include <QToolButton>
46 #include <QMenu>
47 
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  mExpressionContext.setFeature( ft );
749 
750  // Recheck visibility for all containers which are controlled by this value
751  Q_FOREACH ( ContainerInformation* info, mContainerInformationDependency.value( eww->field().name() ) )
752  {
753  info->apply( &mExpressionContext );
754  }
755  }
756 }
757 
758 bool QgsAttributeForm::currentFormFeature( QgsFeature &feature )
759 {
760  bool rc = true;
761  feature = QgsFeature( mFeature );
762  QgsAttributes src = feature.attributes();
763  QgsAttributes dst = feature.attributes();
764 
765  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
766  {
767  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
768  if ( eww && dst.count() > eww->fieldIdx() )
769  {
770  QVariant dstVar = dst.at( eww->fieldIdx() );
771  QVariant srcVar = eww->value();
772  // need to check dstVar.isNull() != srcVar.isNull()
773  // otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
774  if (( dstVar != srcVar || dstVar.isNull() != srcVar.isNull() ) && srcVar.isValid() && !mLayer->editFormConfig()->readOnly( eww->fieldIdx() ) )
775  dst[eww->fieldIdx()] = srcVar;
776  }
777  else
778  {
779  rc = false;
780  break;
781  }
782  }
783 
784  feature.setAttributes( dst );
785 
786  return rc;
787 }
788 
789 void QgsAttributeForm::clearInvalidConstraintsMessage()
790 {
791  mInvalidConstraintMessage->hide();
792  mInvalidConstraintMessage->clear();
793  mInvalidConstraintMessage->setStyleSheet( QString() );
794 }
795 
796 void QgsAttributeForm::displayInvalidConstraintMessage( const QStringList& f,
797  const QStringList& d )
798 {
799  clearInvalidConstraintsMessage();
800 
801  // show only the third first errors (to avoid a too long label)
802  int max = 3;
803  int size = f.size() > max ? max : f.size();
804  QString descriptions;
805  for ( int i = 0; i < size; i++ )
806  descriptions += QString( "<li>%1: <i>%2</i></li>" ).arg( f[i] ).arg( d[i] );
807 
808  QString icPath = QgsApplication::iconPath( "/mIconWarning.svg" );
809 
810  QString title = QString( "<img src=\"%1\"> <b>%2:" ).arg( icPath ).arg( tr( "Invalid fields" ) );
811  QString msg = QString( "%1</b><ul>%2</ul>" ).arg( title ).arg( descriptions ) ;
812 
813  mInvalidConstraintMessage->show();
814  mInvalidConstraintMessage->setText( msg );
815  mInvalidConstraintMessage->setStyleSheet( "QLabel { background-color : #ffc800; }" );
816 }
817 
818 void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation* info )
819 {
820  mContainerVisibilityInformation.append( info );
821  Q_FOREACH ( const QString& col, info->expression.referencedColumns() )
822  {
823  mContainerInformationDependency[ col ].append( info );
824  }
825 }
826 
827 bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields,
828  QStringList &descriptions )
829 {
830  bool valid( true );
831 
832  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
833  {
834  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
835  if ( eww )
836  {
837  if ( ! eww->isValidConstraint() )
838  {
839  invalidFields.append( eww->field().name() );
840 
841  QString desc = eww->layer()->editFormConfig()->expressionDescription( eww->fieldIdx() );
842  descriptions.append( desc );
843 
844  valid = false; // continue to get all invalif fields
845  }
846  }
847  }
848 
849  return valid;
850 }
851 
852 void QgsAttributeForm::onAttributeAdded( int idx )
853 {
854  mPreventFeatureRefresh = false;
855  if ( mFeature.isValid() )
856  {
857  QgsAttributes attrs = mFeature.attributes();
858  attrs.insert( idx, QVariant( layer()->fields().at( idx ).type() ) );
859  mFeature.setFields( layer()->fields() );
860  mFeature.setAttributes( attrs );
861  }
862  init();
863  setFeature( mFeature );
864 }
865 
866 void QgsAttributeForm::onAttributeDeleted( int idx )
867 {
868  mPreventFeatureRefresh = false;
869  if ( mFeature.isValid() )
870  {
871  QgsAttributes attrs = mFeature.attributes();
872  attrs.remove( idx );
873  mFeature.setFields( layer()->fields() );
874  mFeature.setAttributes( attrs );
875  }
876  init();
877  setFeature( mFeature );
878 }
879 
880 void QgsAttributeForm::onUpdatedFields()
881 {
882  mPreventFeatureRefresh = false;
883  if ( mFeature.isValid() )
884  {
885  QgsAttributes attrs( layer()->fields().size() );
886  for ( int i = 0; i < layer()->fields().size(); i++ )
887  {
888  int idx = mFeature.fields()->indexFromName( layer()->fields().at( i ).name() );
889  if ( idx != -1 )
890  {
891  attrs[i] = mFeature.attributes().at( idx );
892  if ( mFeature.attributes().at( idx ).type() != layer()->fields().at( i ).type() )
893  {
894  attrs[i].convert( layer()->fields().at( i ).type() );
895  }
896  }
897  else
898  {
899  attrs[i] = QVariant( layer()->fields().at( i ).type() );
900  }
901  }
902  mFeature.setFields( layer()->fields() );
903  mFeature.setAttributes( attrs );
904  }
905  init();
906  setFeature( mFeature );
907 }
908 
909 void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint,
910  const QString& description, const QString& err, bool ok )
911 {
912  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( sender() );
913  Q_ASSERT( eww );
914 
915  QLabel* buddy = mBuddyMap.value( eww->widget() );
916 
917  if ( buddy )
918  {
919  QString tooltip = tr( "Description: " ) + description + "\n" +
920  tr( "Raw expression: " ) + constraint + "\n" + tr( "Constraint: " ) + err;
921  buddy->setToolTip( tooltip );
922 
923  if ( !buddy->property( "originalText" ).isValid() )
924  buddy->setProperty( "originalText", buddy->text() );
925 
926  QString text = buddy->property( "originalText" ).toString();
927 
928  if ( !ok )
929  {
930  // not good
931  buddy->setText( QString( "%1<font color=\"red\">*</font>" ).arg( text ) );
932  }
933  else
934  {
935  // good
936  buddy->setText( QString( "%1<font color=\"green\">*</font>" ).arg( text ) );
937  }
938  }
939 }
940 
941 void QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper* w,
943 {
944  QString name = w->field().name();
945 
946  // for each widget in the current form
947  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
948  {
949  // get the wrapper
950  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
951  if ( eww )
952  {
953  // compare name to not compare w to itself
954  QString ewwName = eww->field().name();
955  if ( name != ewwName )
956  {
957  // get expression and referencedColumns
958  QgsExpression expr = eww->layer()->editFormConfig()->expression( eww->fieldIdx() );
959 
960  Q_FOREACH ( const QString& colName, expr.referencedColumns() )
961  {
962  if ( name == colName )
963  {
964  wDeps.append( eww );
965  break;
966  }
967  }
968  }
969  }
970  }
971 }
972 
973 void QgsAttributeForm::preventFeatureRefresh()
974 {
975  mPreventFeatureRefresh = true;
976 }
977 
979 {
980  if ( mPreventFeatureRefresh || mLayer->isEditable() || !mFeature.isValid() )
981  return;
982 
983  // reload feature if layer changed although not editable
984  // (datasource probably changed bypassing QgsVectorLayer)
985  if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeature.id() ) ).nextFeature( mFeature ) )
986  return;
987 
988  init();
989  setFeature( mFeature );
990 }
991 
992 void QgsAttributeForm::synchronizeEnabledState()
993 {
994  bool isEditable = ( mFeature.isValid()
995  || mMode == AddFeatureMode
996  || mMode == MultiEditMode ) && mLayer->isEditable();
997 
998  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
999  {
1000  bool fieldEditable = true;
1001  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
1002  if ( eww )
1003  {
1004  fieldEditable = !mLayer->editFormConfig()->readOnly( eww->fieldIdx() ) &&
1006  FID_IS_NEW( mFeature.id() ) );
1007  }
1008  ww->setEnabled( isEditable && fieldEditable );
1009  }
1010 
1011  // push a message and disable the OK button if constraints are invalid
1012  clearInvalidConstraintsMessage();
1013 
1014  if ( mMode != SearchMode )
1015  {
1016  QStringList invalidFields, descriptions;
1017  bool validConstraint = currentFormValidConstraints( invalidFields, descriptions );
1018 
1019  if ( ! validConstraint )
1020  displayInvalidConstraintMessage( invalidFields, descriptions );
1021 
1022  isEditable = isEditable & validConstraint;
1023  }
1024 
1025  // change ok button status
1026  QPushButton* okButton = mButtonBox->button( QDialogButtonBox::Ok );
1027  if ( okButton )
1028  okButton->setEnabled( isEditable );
1029 }
1030 
1031 void QgsAttributeForm::init()
1032 {
1033  QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
1034 
1035  // Cleanup of any previously shown widget, we start from scratch
1036  QWidget* formWidget = nullptr;
1037 
1038  bool buttonBoxVisible = true;
1039  // Cleanup button box but preserve visibility
1040  if ( mButtonBox )
1041  {
1042  buttonBoxVisible = mButtonBox->isVisible();
1043  delete mButtonBox;
1044  mButtonBox = nullptr;
1045  }
1046 
1047  if ( mSearchButtonBox )
1048  {
1049  delete mSearchButtonBox;
1050  mSearchButtonBox = nullptr;
1051  }
1052 
1053  qDeleteAll( mWidgets );
1054  mWidgets.clear();
1055 
1056  while ( QWidget* w = this->findChild<QWidget*>() )
1057  {
1058  delete w;
1059  }
1060  delete layout();
1061 
1062  QVBoxLayout* vl = new QVBoxLayout();
1063  vl->setMargin( 0 );
1064  vl->setContentsMargins( 0, 0, 0, 0 );
1065  mMessageBar = new QgsMessageBar( this );
1066  mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1067  vl->addWidget( mMessageBar );
1068 
1069  mInvalidConstraintMessage = new QLabel( this );
1070  mInvalidConstraintMessage->hide();
1071  vl->addWidget( mInvalidConstraintMessage );
1072 
1073  setLayout( vl );
1074 
1075  // Get a layout
1076  QGridLayout* layout = new QGridLayout();
1077  QWidget* container = new QWidget();
1078  container->setLayout( layout );
1079  vl->addWidget( container );
1080 
1081  mFormEditorWidgets.clear();
1082 
1083  // a bar to warn the user with non-blocking messages
1084  setContentsMargins( 0, 0, 0, 0 );
1085 
1086  // Try to load Ui-File for layout
1087  if ( mContext.allowCustomUi() && mLayer->editFormConfig()->layout() == QgsEditFormConfig::UiFileLayout &&
1088  !mLayer->editFormConfig()->uiForm().isEmpty() )
1089  {
1090  QFile file( mLayer->editFormConfig()->uiForm() );
1091 
1092  if ( file.open( QFile::ReadOnly ) )
1093  {
1094  QUiLoader loader;
1095 
1096  QFileInfo fi( mLayer->editFormConfig()->uiForm() );
1097  loader.setWorkingDirectory( fi.dir() );
1098  formWidget = loader.load( &file, this );
1099  formWidget->setWindowFlags( Qt::Widget );
1100  layout->addWidget( formWidget );
1101  formWidget->show();
1102  file.close();
1103  mButtonBox = findChild<QDialogButtonBox*>();
1104  createWrappers();
1105 
1106  formWidget->installEventFilter( this );
1107  }
1108  }
1109 
1110  QgsTabWidget* tabWidget = nullptr;
1111 
1112  // Tab layout
1113  if ( !formWidget && mLayer->editFormConfig()->layout() == QgsEditFormConfig::TabLayout )
1114  {
1115  int row = 0;
1116  int column = 0;
1117  int columnCount = 1;
1118 
1119  Q_FOREACH ( QgsAttributeEditorElement* widgDef, mLayer->editFormConfig()->tabs() )
1120  {
1122  {
1123  QgsAttributeEditorContainer* containerDef = dynamic_cast<QgsAttributeEditorContainer*>( widgDef );
1124  if ( !containerDef )
1125  continue;
1126 
1127  if ( containerDef->isGroupBox() )
1128  {
1129  tabWidget = nullptr;
1130  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext );
1131  layout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1132  registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().data() ) );
1133  column += 2;
1134  }
1135  else
1136  {
1137  if ( !tabWidget )
1138  {
1139  tabWidget = new QgsTabWidget();
1140  layout->addWidget( tabWidget, row, column, 1, 2 );
1141  column += 2;
1142  }
1143 
1144  QWidget* tabPage = new QWidget( tabWidget );
1145 
1146  tabWidget->addTab( tabPage, widgDef->name() );
1147 
1148  if ( containerDef->visibilityExpression().enabled() )
1149  {
1150  registerContainerInformation( new ContainerInformation( tabWidget, tabPage, containerDef->visibilityExpression().data() ) );
1151  }
1152  QGridLayout* tabPageLayout = new QGridLayout();
1153  tabPage->setLayout( tabPageLayout );
1154 
1155  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
1156  tabPageLayout->addWidget( widgetInfo.widget );
1157  }
1158  }
1159  else
1160  {
1161  tabWidget = nullptr;
1162  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, container, mLayer, mContext );
1163  QLabel* label = new QLabel( widgetInfo.labelText );
1164  if ( columnCount > 1 && !widgetInfo.labelOnTop )
1165  {
1166  label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1167  }
1168 
1169  label->setBuddy( widgetInfo.widget );
1170 
1171  if ( !widgetInfo.showLabel )
1172  {
1173  QVBoxLayout* c = new QVBoxLayout();
1174  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1175  c->addWidget( widgetInfo.widget );
1176  layout->addLayout( c, row, column, 1, 2 );
1177  column += 2;
1178  }
1179  else if ( widgetInfo.labelOnTop )
1180  {
1181  QVBoxLayout* c = new QVBoxLayout();
1182  label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1183  c->addWidget( label );
1184  c->addWidget( widgetInfo.widget );
1185  layout->addLayout( c, row, column, 1, 2 );
1186  column += 2;
1187  }
1188  else
1189  {
1190  layout->addWidget( label, row, column++ );
1191  layout->addWidget( widgetInfo.widget, row, column++ );
1192  }
1193  }
1194 
1195  if ( column >= columnCount * 2 )
1196  {
1197  column = 0;
1198  row += 1;
1199  }
1200  }
1201  formWidget = container;
1202  }
1203 
1204  // Autogenerate Layout
1205  // If there is still no layout loaded (defined as autogenerate or other methods failed)
1206  if ( !formWidget )
1207  {
1208  formWidget = new QWidget( this );
1209  QGridLayout* gridLayout = new QGridLayout( formWidget );
1210  formWidget->setLayout( gridLayout );
1211 
1212  if ( mContext.formMode() != QgsAttributeEditorContext::Embed )
1213  {
1214  // put the form into a scroll area to nicely handle cases with lots of attributes
1215  QScrollArea* scrollArea = new QScrollArea( this );
1216  scrollArea->setWidget( formWidget );
1217  scrollArea->setWidgetResizable( true );
1218  scrollArea->setFrameShape( QFrame::NoFrame );
1219  scrollArea->setFrameShadow( QFrame::Plain );
1220  scrollArea->setFocusProxy( this );
1221  layout->addWidget( scrollArea );
1222  }
1223  else
1224  {
1225  layout->addWidget( formWidget );
1226  }
1227 
1228  int row = 0;
1229  Q_FOREACH ( const QgsField& field, mLayer->fields().toList() )
1230  {
1231  int idx = mLayer->fieldNameIndex( field.name() );
1232  if ( idx < 0 )
1233  continue;
1234 
1235  //show attribute alias if available
1236  QString fieldName = mLayer->attributeDisplayName( idx );
1237 
1238  const QString widgetType = mLayer->editFormConfig()->widgetType( idx );
1239 
1240  if ( widgetType == "Hidden" )
1241  continue;
1242 
1243  const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig()->widgetConfig( idx );
1244  bool labelOnTop = mLayer->editFormConfig()->labelOnTop( idx );
1245 
1246  // This will also create the widget
1247  QLabel *l = new QLabel( fieldName );
1248  QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, idx, widgetConfig, nullptr, this, mContext );
1249 
1250  QWidget* w = nullptr;
1251  if ( eww )
1252  {
1253  QgsAttributeFormEditorWidget* formWidget = new QgsAttributeFormEditorWidget( eww, this );
1254  w = formWidget;
1255  mFormEditorWidgets.insert( idx, formWidget );
1256  formWidget->createSearchWidgetWrappers( widgetType, idx, widgetConfig, mContext );
1257 
1258  l->setBuddy( eww->widget() );
1259  }
1260  else
1261  {
1262  w = new QLabel( QString( "<p style=\"color: red; font-style: italic;\">Failed to create widget with type '%1'</p>" ).arg( widgetType ) );
1263  }
1264 
1265 
1266  if ( w )
1267  w->setObjectName( field.name() );
1268 
1269  if ( eww )
1270  addWidgetWrapper( eww );
1271 
1272  if ( labelOnTop )
1273  {
1274  gridLayout->addWidget( l, row++, 0, 1, 2 );
1275  gridLayout->addWidget( w, row++, 0, 1, 2 );
1276  }
1277  else
1278  {
1279  gridLayout->addWidget( l, row, 0 );
1280  gridLayout->addWidget( w, row++, 1 );
1281  }
1282  }
1283 
1284  Q_FOREACH ( const QgsRelation& rel, QgsProject::instance()->relationManager()->referencedRelations( mLayer ) )
1285  {
1286  QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, rel, nullptr, this );
1287  QgsEditorWidgetConfig cfg = mLayer->editFormConfig()->widgetConfig( rel.id() );
1288  rww->setConfig( cfg );
1289  rww->setContext( mContext );
1290  gridLayout->addWidget( rww->widget(), row++, 0, 1, 2 );
1291  mWidgets.append( rww );
1292  }
1293 
1294  if ( QgsProject::instance()->relationManager()->referencedRelations( mLayer ).isEmpty() )
1295  {
1296  QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
1297  gridLayout->addItem( spacerItem, row, 0 );
1298  gridLayout->setRowStretch( row, 1 );
1299  row++;
1300  }
1301  }
1302 
1303  if ( !mButtonBox )
1304  {
1305  mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
1306  mButtonBox->setObjectName( "buttonBox" );
1307  layout->addWidget( mButtonBox, layout->rowCount(), 0, 1, layout->columnCount() );
1308  }
1309  mButtonBox->setVisible( buttonBoxVisible );
1310 
1311  if ( !mSearchButtonBox )
1312  {
1313  mSearchButtonBox = new QWidget();
1314  QHBoxLayout* boxLayout = new QHBoxLayout();
1315  boxLayout->setMargin( 0 );
1316  boxLayout->setContentsMargins( 0, 0, 0, 0 );
1317  mSearchButtonBox->setLayout( boxLayout );
1318  mSearchButtonBox->setObjectName( "searchButtonBox" );
1319 
1320  QPushButton* clearButton = new QPushButton( tr( "&Reset form" ), mSearchButtonBox );
1321  connect( clearButton, SIGNAL( clicked( bool ) ), this, SLOT( resetSearch() ) );
1322  boxLayout->addWidget( clearButton );
1323  boxLayout->addStretch( 1 );
1324 
1325  QToolButton* selectButton = new QToolButton();
1326  selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1327  selectButton->setText( tr( "&Select features" ) );
1328  selectButton->setPopupMode( QToolButton::MenuButtonPopup );
1329  connect( selectButton, SIGNAL( clicked( bool ) ), this, SLOT( searchSetSelection() ) );
1330  QMenu* selectMenu = new QMenu( selectButton );
1331  QAction* selectAction = new QAction( tr( "Select features" ), selectMenu );
1332  connect( selectAction, SIGNAL( triggered( bool ) ), this, SLOT( searchSetSelection() ) );
1333  selectMenu->addAction( selectAction );
1334  QAction* addSelectAction = new QAction( tr( "Add to current selection" ), selectMenu );
1335  connect( addSelectAction, SIGNAL( triggered( bool ) ), this, SLOT( searchAddToSelection() ) );
1336  selectMenu->addAction( addSelectAction );
1337  QAction* filterSelectAction = new QAction( tr( "Filter current selection" ), selectMenu );
1338  connect( filterSelectAction, SIGNAL( triggered( bool ) ), this, SLOT( searchIntersectSelection() ) );
1339  selectMenu->addAction( filterSelectAction );
1340  QAction* deselectAction = new QAction( tr( "Remove from current selection" ), selectMenu );
1341  connect( deselectAction, SIGNAL( triggered( bool ) ), this, SLOT( searchRemoveFromSelection() ) );
1342  selectMenu->addAction( deselectAction );
1343  selectButton->setMenu( selectMenu );
1344  boxLayout->addWidget( selectButton );
1345 
1346  if ( mContext.formMode() == QgsAttributeEditorContext::Embed )
1347  {
1348  QToolButton* filterButton = new QToolButton();
1349  filterButton->setText( tr( "Filter features" ) );
1350  filterButton->setPopupMode( QToolButton::MenuButtonPopup );
1351  filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
1352  connect( filterButton, SIGNAL( clicked( bool ) ), this, SLOT( filterTriggered() ) );
1353  QMenu* filterMenu = new QMenu( filterButton );
1354  QAction* filterAndAction = new QAction( tr( "Filter within (\"AND\")" ), filterMenu );
1355  connect( filterAndAction, SIGNAL( triggered( bool ) ), this, SLOT( filterAndTriggered() ) );
1356  filterMenu->addAction( filterAndAction );
1357  QAction* filterOrAction = new QAction( tr( "Extend filter (\"OR\")" ), filterMenu );
1358  connect( filterOrAction, SIGNAL( triggered( bool ) ), this, SLOT( filterOrTriggered() ) );
1359  filterMenu->addAction( filterOrAction );
1360  filterButton->setMenu( filterMenu );
1361  boxLayout->addWidget( filterButton );
1362  }
1363  else
1364  {
1365  QPushButton* closeButton = new QPushButton( tr( "Close" ), mSearchButtonBox );
1366  connect( closeButton, SIGNAL( clicked( bool ) ), this, SIGNAL( closed() ) );
1367  closeButton->setShortcut( Qt::Key_Escape );
1368  boxLayout->addWidget( closeButton );
1369  }
1370 
1371  layout->addWidget( mSearchButtonBox );
1372  }
1373  mSearchButtonBox->setVisible( mMode == SearchMode );
1374 
1375  afterWidgetInit();
1376 
1377  connect( mButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
1378  connect( mButtonBox, SIGNAL( rejected() ), this, SLOT( resetValues() ) );
1379 
1380  connect( mLayer, SIGNAL( editingStarted() ), this, SLOT( synchronizeEnabledState() ) );
1381  connect( mLayer, SIGNAL( editingStopped() ), this, SLOT( synchronizeEnabledState() ) );
1382 
1383  Q_FOREACH ( QgsAttributeFormInterface* iface, mInterfaces )
1384  {
1385  iface->initForm();
1386  }
1387 
1388  if ( mContext.formMode() == QgsAttributeEditorContext::Embed || mMode == SearchMode )
1389  {
1390  hideButtonBox();
1391  }
1392 
1394 }
1395 
1396 void QgsAttributeForm::cleanPython()
1397 {
1398  if ( !mPyFormVarName.isNull() )
1399  {
1400  QString expr = QString( "if locals().has_key('%1'): del %1\n" ).arg( mPyFormVarName );
1401  QgsPythonRunner::run( expr );
1402  }
1403 }
1404 
1405 void QgsAttributeForm::initPython()
1406 {
1407  cleanPython();
1408 
1409  // Init Python, if init function is not empty and the combo indicates
1410  // the source for the function code
1411  if ( !mLayer->editFormConfig()->initFunction().isEmpty()
1413  {
1414 
1415  QString initFunction = mLayer->editFormConfig()->initFunction();
1416  QString initFilePath = mLayer->editFormConfig()->initFilePath();
1417  QString initCode;
1418 
1419  switch ( mLayer->editFormConfig()->initCodeSource() )
1420  {
1422  if ( ! initFilePath.isEmpty() )
1423  {
1424  QFile inputFile( initFilePath );
1425 
1426  if ( inputFile.open( QFile::ReadOnly ) )
1427  {
1428  // Read it into a string
1429  QTextStream inf( &inputFile );
1430  initCode = inf.readAll();
1431  inputFile.close();
1432  }
1433  else // The file couldn't be opened
1434  {
1435  QgsLogger::warning( QString( "The external python file path %1 could not be opened!" ).arg( initFilePath ) );
1436  }
1437  }
1438  else
1439  {
1440  QgsLogger::warning( QString( "The external python file path is empty!" ) );
1441  }
1442  break;
1443 
1445  initCode = mLayer->editFormConfig()->initCode();
1446  if ( initCode.isEmpty() )
1447  {
1448  QgsLogger::warning( QString( "The python code provided in the dialog is empty!" ) );
1449  }
1450  break;
1451 
1454  default:
1455  // Nothing to do: the function code should be already in the environment
1456  break;
1457  }
1458 
1459  // If we have a function code, run it
1460  if ( ! initCode.isEmpty() )
1461  {
1462  QgsPythonRunner::run( initCode );
1463  }
1464 
1465  QgsPythonRunner::run( "import inspect" );
1466  QString numArgs;
1467 
1468  // Check for eval result
1469  if ( QgsPythonRunner::eval( QString( "len(inspect.getargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
1470  {
1471  static int sFormId = 0;
1472  mPyFormVarName = QString( "_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
1473 
1474  QString form = QString( "%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
1475  .arg( mPyFormVarName )
1476  .arg(( unsigned long ) this );
1477 
1478  QgsPythonRunner::run( form );
1479 
1480  QgsDebugMsg( QString( "running featureForm init: %1" ).arg( mPyFormVarName ) );
1481 
1482  // Legacy
1483  if ( numArgs == "3" )
1484  {
1485  addInterface( new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName, this ) );
1486  }
1487  else
1488  {
1489  // If we get here, it means that the function doesn't accept three arguments
1490  QMessageBox msgBox;
1491  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 ) );
1492  msgBox.exec();
1493 #if 0
1494  QString expr = QString( "%1(%2)" )
1495  .arg( mLayer->editFormInit() )
1496  .arg( mPyFormVarName );
1497  QgsAttributeFormInterface* iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface*>( expr, "QgsAttributeFormInterface" );
1498  if ( iface )
1499  addInterface( iface );
1500 #endif
1501  }
1502  }
1503  else
1504  {
1505  // If we get here, it means that inspect couldn't find the function
1506  QMessageBox msgBox;
1507  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 ) );
1508  msgBox.exec();
1509  }
1510  }
1511 }
1512 
1513 QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement* widgetDef, QWidget* parent, QgsVectorLayer* vl, QgsAttributeEditorContext& context )
1514 {
1515  WidgetInfo newWidgetInfo;
1516 
1517  switch ( widgetDef->type() )
1518  {
1520  {
1521  const QgsAttributeEditorField* fieldDef = dynamic_cast<const QgsAttributeEditorField*>( widgetDef );
1522  if ( !fieldDef )
1523  break;
1524 
1525  int fldIdx = vl->fieldNameIndex( fieldDef->name() );
1526  if ( fldIdx < vl->fields().count() && fldIdx >= 0 )
1527  {
1528  const QString widgetType = mLayer->editFormConfig()->widgetType( fldIdx );
1529  const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig()->widgetConfig( fldIdx );
1530 
1531  QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, fldIdx, widgetConfig, nullptr, this, mContext );
1533  mFormEditorWidgets.insert( fldIdx, w );
1534 
1535  w->createSearchWidgetWrappers( widgetType, fldIdx, widgetConfig, mContext );
1536 
1537  newWidgetInfo.widget = w;
1538  addWidgetWrapper( eww );
1539 
1540  newWidgetInfo.widget->setObjectName( mLayer->fields().at( fldIdx ).name() );
1541  }
1542 
1543  newWidgetInfo.labelOnTop = mLayer->editFormConfig()->labelOnTop( fieldDef->idx() );
1544  newWidgetInfo.labelText = mLayer->attributeDisplayName( fieldDef->idx() );
1545  newWidgetInfo.showLabel = widgetDef->showLabel();
1546 
1547  break;
1548  }
1549 
1551  {
1552  const QgsAttributeEditorRelation* relDef = static_cast<const QgsAttributeEditorRelation*>( widgetDef );
1553 
1554  QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, relDef->relation(), nullptr, this );
1555  QgsEditorWidgetConfig cfg = mLayer->editFormConfig()->widgetConfig( relDef->relation().id() );
1556  rww->setConfig( cfg );
1557  rww->setContext( context );
1558  newWidgetInfo.widget = rww->widget();
1559  rww->setShowLabel( relDef->showLabel() );
1560  rww->setShowLinkButton( relDef->showLinkButton() );
1561  rww->setShowUnlinkButton( relDef->showUnlinkButton() );
1562  mWidgets.append( rww );
1563  newWidgetInfo.labelText = QString::null;
1564  newWidgetInfo.labelOnTop = true;
1565  break;
1566  }
1567 
1569  {
1570  const QgsAttributeEditorContainer* container = dynamic_cast<const QgsAttributeEditorContainer*>( widgetDef );
1571  if ( !container )
1572  break;
1573 
1574  int columnCount = container->columnCount();
1575 
1576  if ( columnCount <= 0 )
1577  columnCount = 1;
1578 
1579  QWidget* myContainer;
1580  if ( container->isGroupBox() )
1581  {
1582  QGroupBox* groupBox = new QGroupBox( parent );
1583  if ( container->showLabel() )
1584  groupBox->setTitle( container->name() );
1585  myContainer = groupBox;
1586  newWidgetInfo.widget = myContainer;
1587  }
1588  else
1589  {
1590  myContainer = new QWidget();
1591 
1592  if ( context.formMode() != QgsAttributeEditorContext::Embed )
1593  {
1594  QScrollArea *scrollArea = new QScrollArea( parent );
1595 
1596  scrollArea->setWidget( myContainer );
1597  scrollArea->setWidgetResizable( true );
1598  scrollArea->setFrameShape( QFrame::NoFrame );
1599 
1600  newWidgetInfo.widget = scrollArea;
1601  }
1602  else
1603  {
1604  newWidgetInfo.widget = myContainer;
1605  }
1606  }
1607 
1608  QGridLayout* gbLayout = new QGridLayout();
1609  myContainer->setLayout( gbLayout );
1610 
1611  int row = 0;
1612  int column = 0;
1613 
1615 
1616  Q_FOREACH ( QgsAttributeEditorElement* childDef, children )
1617  {
1618  WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
1619 
1620  if ( childDef->type() == QgsAttributeEditorElement::AeTypeContainer )
1621  {
1622  QgsAttributeEditorContainer* containerDef = static_cast<QgsAttributeEditorContainer*>( childDef );
1623  registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().data() ) );
1624  }
1625 
1626  if ( widgetInfo.labelText.isNull() )
1627  {
1628  gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1629  column += 2;
1630  }
1631  else
1632  {
1633  QLabel* mypLabel = new QLabel( widgetInfo.labelText );
1634  if ( columnCount > 1 && !widgetInfo.labelOnTop )
1635  {
1636  mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1637  }
1638 
1639  mypLabel->setBuddy( widgetInfo.widget );
1640 
1641  if ( widgetInfo.labelOnTop )
1642  {
1643  QVBoxLayout* c = new QVBoxLayout();
1644  mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1645  c->layout()->addWidget( mypLabel );
1646  c->layout()->addWidget( widgetInfo.widget );
1647  gbLayout->addLayout( c, row, column, 1, 2 );
1648  column += 2;
1649  }
1650  else
1651  {
1652  gbLayout->addWidget( mypLabel, row, column++ );
1653  gbLayout->addWidget( widgetInfo.widget, row, column++ );
1654  }
1655  }
1656 
1657  if ( column >= columnCount * 2 )
1658  {
1659  column = 0;
1660  row += 1;
1661  }
1662  }
1663  QWidget* spacer = new QWidget();
1664  spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
1665  gbLayout->addWidget( spacer, ++row, 0 );
1666  gbLayout->setRowStretch( row, 1 );
1667 
1668  newWidgetInfo.labelText = QString::null;
1669  newWidgetInfo.labelOnTop = true;
1670  break;
1671  }
1672 
1673  default:
1674  QgsDebugMsg( "Unknown attribute editor widget type encountered..." );
1675  break;
1676  }
1677 
1678  newWidgetInfo.showLabel = widgetDef->showLabel();
1679 
1680  return newWidgetInfo;
1681 }
1682 
1683 void QgsAttributeForm::addWidgetWrapper( QgsEditorWidgetWrapper* eww )
1684 {
1685  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
1686  {
1687  QgsEditorWidgetWrapper* meww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
1688  if ( meww )
1689  {
1690  if ( meww->field() == eww->field() )
1691  {
1692  connect( meww, SIGNAL( valueChanged( QVariant ) ), eww, SLOT( setValue( QVariant ) ) );
1693  connect( eww, SIGNAL( valueChanged( QVariant ) ), meww, SLOT( setValue( QVariant ) ) );
1694  break;
1695  }
1696  }
1697  }
1698 
1699  mWidgets.append( eww );
1700 }
1701 
1702 void QgsAttributeForm::createWrappers()
1703 {
1704  QList<QWidget*> myWidgets = findChildren<QWidget*>();
1705  const QList<QgsField> fields = mLayer->fields().toList();
1706 
1707  Q_FOREACH ( QWidget* myWidget, myWidgets )
1708  {
1709  // Check the widget's properties for a relation definition
1710  QVariant vRel = myWidget->property( "qgisRelation" );
1711  if ( vRel.isValid() )
1712  {
1714  QgsRelation relation = relMgr->relation( vRel.toString() );
1715  if ( relation.isValid() )
1716  {
1717  QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
1718  rww->setConfig( mLayer->editFormConfig()->widgetConfig( relation.id() ) );
1719  rww->setContext( mContext );
1720  rww->widget(); // Will initialize the widget
1721  mWidgets.append( rww );
1722  }
1723  }
1724  else
1725  {
1726  Q_FOREACH ( const QgsField& field, fields )
1727  {
1728  if ( field.name() == myWidget->objectName() )
1729  {
1730  const QString widgetType = mLayer->editFormConfig()->widgetType( field.name() );
1731  const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig()->widgetConfig( field.name() );
1732  int idx = mLayer->fieldNameIndex( field.name() );
1733 
1734  QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, idx, widgetConfig, myWidget, this, mContext );
1735  addWidgetWrapper( eww );
1736  }
1737  }
1738  }
1739  }
1740 }
1741 
1742 void QgsAttributeForm::afterWidgetInit()
1743 {
1744  bool isFirstEww = true;
1745 
1746  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
1747  {
1748  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
1749 
1750  if ( eww )
1751  {
1752  if ( isFirstEww )
1753  {
1754  setFocusProxy( eww->widget() );
1755  isFirstEww = false;
1756  }
1757 
1758  connect( eww, SIGNAL( valueChanged( const QVariant& ) ), this, SLOT( onAttributeChanged( const QVariant& ) ) );
1759  connect( eww, SIGNAL( constraintStatusChanged( QString, QString, QString, bool ) ),
1760  this, SLOT( onConstraintStatusChanged( QString, QString, QString, bool ) ) );
1761  }
1762  }
1763 
1764  // Update buddy widget list
1765  mBuddyMap.clear();
1766  QList<QLabel*> labels = findChildren<QLabel*>();
1767 
1768  Q_FOREACH ( QLabel* label, labels )
1769  {
1770  if ( label->buddy() )
1771  mBuddyMap.insert( label->buddy(), label );
1772  }
1773 }
1774 
1775 
1777 {
1778  Q_UNUSED( object )
1779 
1780  if ( e->type() == QEvent::KeyPress )
1781  {
1782  QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>( e );
1783  if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
1784  {
1785  // Re-emit to this form so it will be forwarded to parent
1786  event( e );
1787  return true;
1788  }
1789  }
1790 
1791  return false;
1792 }
1793 
1794 void QgsAttributeForm::scanForEqualAttributes( QgsFeatureIterator& fit, QSet< int >& mixedValueFields, QHash< int, QVariant >& fieldSharedValues ) const
1795 {
1796  mixedValueFields.clear();
1797  fieldSharedValues.clear();
1798 
1799  QgsFeature f;
1800  bool first = true;
1801  while ( fit.nextFeature( f ) )
1802  {
1803  for ( int i = 0; i < mLayer->fields().count(); ++i )
1804  {
1805  if ( mixedValueFields.contains( i ) )
1806  continue;
1807 
1808  if ( first )
1809  {
1810  fieldSharedValues[i] = f.attribute( i );
1811  }
1812  else
1813  {
1814  if ( fieldSharedValues.value( i ) != f.attribute( i ) )
1815  {
1816  fieldSharedValues.remove( i );
1817  mixedValueFields.insert( i );
1818  }
1819  }
1820  }
1821  first = false;
1822 
1823  if ( mixedValueFields.count() == mLayer->fields().count() )
1824  {
1825  // all attributes are mixed, no need to keep scanning
1826  break;
1827  }
1828  }
1829 }
1830 
1831 
1832 void QgsAttributeForm::layerSelectionChanged()
1833 {
1834  switch ( mMode )
1835  {
1836  case SingleEditMode:
1837  case AddFeatureMode:
1838  case SearchMode:
1839  break;
1840 
1841  case MultiEditMode:
1842  resetMultiEdit( true );
1843  break;
1844  }
1845 }
1846 
1848 {
1849  mIsSettingMultiEditFeatures = true;
1850  mMultiEditFeatureIds = fids;
1851 
1852  if ( fids.isEmpty() )
1853  {
1854  // no selected features
1856  for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
1857  {
1858  wIt.value()->initialize( QVariant() );
1859  }
1860  mIsSettingMultiEditFeatures = false;
1861  return;
1862  }
1863 
1864  QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFids( fids ) );
1865 
1866  // Scan through all features to determine which attributes are initially the same
1867  QSet< int > mixedValueFields;
1868  QHash< int, QVariant > fieldSharedValues;
1869  scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
1870 
1871  // also fetch just first feature
1872  fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFid( *fids.constBegin() ) );
1873  QgsFeature firstFeature;
1874  fit.nextFeature( firstFeature );
1875 
1876  Q_FOREACH ( int field, mixedValueFields )
1877  {
1878  if ( QgsAttributeFormEditorWidget* w = mFormEditorWidgets.value( field, nullptr ) )
1879  {
1880  w->initialize( firstFeature.attribute( field ), true );
1881  }
1882  }
1883  QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
1884  for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
1885  {
1886  if ( QgsAttributeFormEditorWidget* w = mFormEditorWidgets.value( sharedValueIt.key(), nullptr ) )
1887  {
1888  w->initialize( sharedValueIt.value(), false );
1889  }
1890  }
1891  mIsSettingMultiEditFeatures = false;
1892 }
1893 
1895 {
1896  if ( mOwnsMessageBar )
1897  delete mMessageBar;
1898  mOwnsMessageBar = false;
1899  mMessageBar = messageBar;
1900 }
1901 
1902 int QgsAttributeForm::messageTimeout()
1903 {
1904  QSettings settings;
1905  return settings.value( "/qgis/messageTimeout", 5 ).toInt();
1906 }
1907 
1908 void QgsAttributeForm::ContainerInformation::apply( QgsExpressionContext* expressionContext )
1909 {
1910  bool newVisibility = expression.evaluate( expressionContext ).toBool();
1911 
1912  if ( newVisibility != isVisible )
1913  {
1914  if ( tabWidget )
1915  {
1916  tabWidget->setTabVisible( widget, newVisibility );
1917  }
1918  else
1919  {
1920  widget->setVisible( newVisibility );
1921  }
1922 
1923  isVisible = newVisibility;
1924  }
1925 }
QLayout * layout() const
QWidget * buddy() const
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:199
Class for parsing and evaluation of expressions (formerly called "search strings").
Load the python code from an external file.
Use the python code available in the python environment.
void resetValues()
Sets all values to the values of the current feature.
virtual void setEnabled(bool enabled)
Is used to enable or disable the edit functionality of the managed widget.
void resetSearch()
Resets the search/filter form values.
void clear()
Wrapper for iterator of features from vector data provider or vector layer.
void setStyleSheet(const QString &styleSheet)
bool isValid() const
Returns the validity of this relation.
bool notNull(int fieldidx) const
Returns if the field at fieldidx should be treated as NOT NULL value.
Use the python code provided in the dialog.
virtual QLayout * layout()
void setWidget(QWidget *widget)
QString & append(QChar ch)
void setMenu(QMenu *menu)
Type type() const
void setContentsMargins(int left, int top, int right, int bottom)
bool hasChanged() const
Returns true if the widget&#39;s value has been changed since it was initialized.
QgsAttributes attributes() const
Returns the feature&#39;s attributes.
Definition: qgsfeature.cpp:110
int size() const
Return number of items.
Definition: qgsfield.cpp:407
This is an abstract base class for any elements of a drag and drop form.
void setMultiEditFeatureIds(const QgsFeatureIds &fids)
Sets all feature IDs which are to be edited if the form is in multiedit mode.
QString name
Definition: qgsfield.h:52
QgsField field() const
Access the field.
Q_DECL_DEPRECATED void accept()
Alias for save()
void setShowUnlinkButton(bool showUnlinkButton)
Determines if the "unlink feature" button should be shown.
bool enabled() const
Check if this optional is enabled.
Definition: qgsoptional.h:87
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
void append(const T &value)
void addWidget(QWidget *widget, int row, int column, QFlags< Qt::AlignmentFlag > alignment)
void closed()
Emitted when the user selects the close option from the form&#39;s button bar.
void hideButtonBox()
Hides the button box (Ok/Cancel) and enables auto-commit.
Modify current selection to include only select features which match.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
void setFrameShape(Shape)
This class contains context information for attribute editor widgets.
QObject * sender() const
Manages an editor widget Widget and wrapper share the same parent.
QList< QgsAttributeEditorElement *> tabs() const
Returns a list of tabs for EditorLayout::TabLayout.
QWidget * widget() const
QStringList referencedColumns() const
Get list of columns referenced by the expression.
QString & prepend(QChar ch)
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:124
void setVisible(bool visible)
Sets the visibility of the wrapper&#39;s widget.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
const QgsRelation & relation() const
Get the id of the relation which shall be embedded.
bool editable()
Returns if the form is currently in editable mode.
const_iterator constBegin() const
bool save()
Save all the values from the editors to the layer.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
const QObjectList & children() const
Use a layout with tabs and group boxes. Needs to be configured.
void addAction(QAction *action)
void insert(int i, const T &value)
Q_DECL_DEPRECATED void setIsAddDialog(bool isAddDialog)
Toggles the form mode between edit feature and add feature.
bool isVisible() const
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:42
void setAlignment(QFlags< Qt::AlignmentFlag >)
void setAttributes(const QgsAttributes &attrs)
Sets the feature&#39;s attributes.
Definition: qgsfeature.cpp:115
This element will load a field&#39;s widget onto the form.
void setShowLabel(bool showLabel)
Defines if a title lable should be shown for this widget.
QString expression(int idx) const
Returns the constraint expression of a specific field.
This element will load a relation editor onto the form.
Set selection, removing any existing selection.
void clear()
QString join(const QString &separator) const
const Key & key() const
const_iterator insert(const T &value)
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:187
bool addFeature(QgsFeature &feature, bool alsoUpdateExtent=true)
Adds a feature.
QgsRelationManager * relationManager() const
void setWorkingDirectory(const QDir &dir)
void clear()
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
virtual bool isEditable() const override
Returns true if the provider is in editing mode.
void selectByExpression(const QString &expression, SelectBehaviour behaviour=SetSelection)
Select matching features using an expression.
static QgsEditorWidgetRegistry * instance()
This class is a singleton and has therefore to be accessed with this method instead of a constructor...
int count() const
Return number of items.
Definition: qgsfield.cpp:402
QString tr(const char *sourceText, const char *disambiguation, int n)
StandardButton information(QWidget *parent, const QString &title, const QString &text, QFlags< QMessageBox::StandardButton > buttons, StandardButton defaultButton)
QgsVectorLayer * layer()
Returns the layer for which this form is shown.
AttributeEditorType type() const
The type of this element.
QString id() const
A (project-wide) unique id for this relation.
const QgsField & at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:422
int size() const
virtual void setFeature(const QgsFeature &feature)=0
Is called, when the value of the widget needs to be changed.
bool isNull() const
QgsEditFormConfig * editFormConfig() const
Get the configuration of the form used to represent this vector layer.
QgsFields fields() const
Returns the list of fields of this layer.
A widget consisting of both an editor widget and additional widgets for controlling the behaviour of ...
void setBuddy(QWidget *buddy)
double ANALYSIS_EXPORT max(double x, double y)
Returns the maximum of two doubles or the first argument if both are equal.
void setMode(Mode mode)
Sets the current mode of the form.
QSize size() const
void setContext(const QgsAttributeEditorContext &context)
Set the context in which this widget is shown.
const char * name() const
void showButtonBox()
Shows the button box (Ok/Cancel) and disables auto-commit.
void pushMessage(const QString &text, MessageLevel level=INFO, int duration=5)
convenience method for pushing a message to the bar
Definition: qgsmessagebar.h:90
void setConfig(const QgsEditorWidgetConfig &config)
Will set the config of this wrapper to the specified config.
void setEnabled(bool)
void addWidget(QWidget *widget, int stretch, QFlags< Qt::AlignmentFlag > alignment)
QVariantMap QgsEditorWidgetConfig
Holds a set of configuration parameters for a editor widget wrapper.
void append(const T &value)
bool showUnlinkButton() const
Determines if the "unlink feature" button should be shown.
const QgsFields * fields() const
Returns the field map associated with the feature.
Definition: qgsfeature.cpp:188
QVariant property(const char *name) const
void setRowStretch(int row, int stretch)
void setLayout(QLayout *layout)
const_iterator constEnd() const
void installEventFilter(QObject *filterObj)
Do not use python code at all.
int toInt(bool *ok) const
bool isNull() const
const Key & key() const
void setFeature(const QgsFeature &feature)
Update all editors to correspond to a different feature.
bool popWidget(QgsMessageBarItem *item)
Remove the passed widget from the bar (if previously added), then display the next one in the stack i...
QgsEditorWidgetWrapper * create(const QString &widgetId, QgsVectorLayer *vl, int fieldIdx, const QgsEditorWidgetConfig &config, QWidget *editor, QWidget *parent, const QgsAttributeEditorContext &context=QgsAttributeEditorContext())
Create an attribute editor widget wrapper of a given type for a given field.
void filterExpressionSet(const QString &expression, QgsAttributeForm::FilterType type)
Is emitted when a filter expression is set using the form.
QString currentFilterExpression() const
Creates an expression matching the current search filter value and search properties represented in t...
bool isEmpty() const
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setObjectName(const QString &name)
const T & value() const
void setFocusProxy(QWidget *w)
bool isEmpty() const
void setText(const QString &text)
const_iterator constEnd() const
void remove(int i)
bool showLabel() const
Controls if this element should be labeled with a title (field, relation or groupname).
void triggerRepaint()
Will advice the map canvas (and any other interested party) that this layer requires to be repainted...
int addTab(QWidget *page, const QString &label)
This class wraps a request for features to a vector layer (or directly its vector data provider)...
void setOverrideCursor(const QCursor &cursor)
void modeChanged(QgsAttributeForm::Mode mode)
Emitted when the form changes mode.
QString expressionDescription(int idx) const
Returns the constraint expression description of a specific filed.
void destroyEditCommand()
Destroy active command and reverts all changes in it.
void restoreOverrideCursor()
void refreshFeature()
reload current feature
virtual void setValue(const QVariant &value)=0
Is called, when the value of the widget needs to be changed.
const T & value() const
QgsOptionalExpression visibilityExpression() const
An expression that controls the visibility of this container.
void setShortcut(const QKeySequence &key)
int idx() const
Return the index of the field.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:44
void hide()
int remove(const Key &key)
int count() const
virtual int capabilities() const
Returns a bitmask containing the supported capabilities Note, some capabilities may change depending ...
void beforeSave(bool &ok)
Will be emitted before the feature is saved.
void setMargin(int margin)
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
void disconnectButtonBox()
Disconnects the button box (Ok/Cancel) from the accept/resetValues slots If this method is called...
void setSizePolicy(QSizePolicy)
void addWidget(QWidget *w)
bool eventFilter(QObject *object, QEvent *event) override
Intercepts keypress on custom form (escape should not close it)
Q_DECL_DEPRECATED void setFields(const QgsFields *fields, bool initAttributes=false)
Assign a field map with the feature to allow attribute access by attribute name.
Definition: qgsfeature.cpp:173
Q_DECL_DEPRECATED bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &value, bool emitSignal)
Changes an attribute value (but does not commit it)
void endEditCommand()
Finish edit command and add it to undo/redo stack.
QVariant currentValue() const
Returns the current value of the attached editor widget.
void clear()
const T value(const Key &key) const
QgsFeatureId id() const
Get the feature ID for this feature.
Definition: qgsfeature.cpp:65
int key() const
static bool eval(const QString &command, QString &result)
Eval a python statement.
This class helps to support legacy open form scripts to be compatible with the new QgsAttributeForm s...
void featureSaved(const QgsFeature &feature)
Is emitted, when a feature is changed or added.
void setWidgetResizable(bool resizable)
bool isValidConstraint() const
Get the current constraint status.
virtual void close()
FormMode formMode() const
Returns the form mode.
const_iterator constBegin() const
bool contains(const T &value) const
virtual bool acceptChanges(const QgsFeature &feature)
void setFrameShadow(Shadow)
const QgsFeatureIds & selectedFeaturesIds() const
Return reference to identifiers of selected features.
void setValid(bool validity)
Sets the validity of the feature.
Definition: qgsfeature.cpp:204
void setMode(Mode mode)
Sets the current mode for the widget.
QString initCode() const
Get python code for edit form initialization.
SelectBehaviour
Selection behaviour.
void addLayout(QLayout *layout, int row, int column, QFlags< Qt::AlignmentFlag > alignment)
Q_DECL_DEPRECATED QString editFormInit() const
Get python function for edit form initialization.
Add selection to current selection.
void setWindowFlags(QFlags< Qt::WindowType > type)
const T & at(int i) const
QVariant value(const QString &key, const QVariant &defaultValue) const
const_iterator constBegin() const
QgsAttributeForm(QgsVectorLayer *vl, const QgsFeature &feature=QgsFeature(), const QgsAttributeEditorContext &context=QgsAttributeEditorContext(), QWidget *parent=nullptr)
int indexFromName(const QString &name) const
Look up field&#39;s index from name. Returns -1 on error.
Definition: qgsfield.cpp:461
int rowCount() const
QgsEditorWidgetConfig widgetConfig(int fieldIdx) const
Get the configuration for the editor widget used to represent the field at the given index...
void pushItem(QgsMessageBarItem *item)
Display a message item on the bar after hiding the currently visible one and putting it in a stack...
void changeAttribute(const QString &field, const QVariant &value)
Call this to change the content of a given attribute.
void attributeChanged(const QString &attribute, const QVariant &value)
Notifies about changes of attributes.
void setMessageBar(QgsMessageBar *messageBar)
Sets the message bar to display feedback from the form in.
bool allowCustomUi() const
Returns true if the attribute editor should permit use of custom UI forms.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a python statement.
void updateConstraint(const QgsFeature &featureContext)
Update constraint.
void addStretch(int stretch)
void setTitle(const QString &title)
Load a .ui file for the layout. Needs to be configured.
This class manages a set of relations between layers.
int columnCount() const
Get the number of columns in this group.
void addItem(QLayoutItem *item, int row, int column, int rowSpan, int columnSpan, QFlags< Qt::AlignmentFlag > alignment)
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:382
QWidget(QWidget *parent, QFlags< Qt::WindowType > f)
virtual QVariant value() const =0
Will be used to access the widget&#39;s value.
T data() const
Access the payload data.
Definition: qgsoptional.h:117
void setPopupMode(ToolButtonPopupMode mode)
QList< QgsField > toList() const
Utility function to return a list of QgsField instances.
Definition: qgsfield.cpp:466
Mode mode() const
Returns the current mode of the form.
int count(const T &value) const
void createSearchWidgetWrappers(const QString &widgetId, int fieldIdx, const QgsEditorWidgetConfig &config, const QgsAttributeEditorContext &context=QgsAttributeEditorContext())
Creates the search widget wrappers for the widget used when the form is in search mode...
This is a container for attribute editors, used to group them visually in the attribute form if it is...
QWidget * load(QIODevice *device, QWidget *parentWidget)
void addInterface(QgsAttributeFormInterface *iface)
Takes ownership.
bool isEmpty() const
qint64 QgsFeatureId
Definition: qgsfeature.h:31
void setText(const QString &text)
QList< QgsAttributeEditorElement * > children() const
Get a list of the children elements of this container.
bool isValid() const
Remove from current selection.
QPushButton * button(StandardButton which) const
#define FID_IS_NEW(fid)
Definition: qgsfeature.h:87
const QgsFeature & feature()
bool setProperty(const char *name, const QVariant &value)
iterator insert(const Key &key, const T &value)
void show()
bool isEmpty() const
QWidget * widget()
Access the widget managed by this wrapper.
bool readOnly(int idx) const
This returns true if the field is manually set to read only or if the field does not support editing ...
void setToolTip(const QString &)
virtual bool isGroupBox() const
Returns if this container is going to be rendered as a group box.
void changesCommitted()
Called when field values have been committed;.
QgsVectorDataProvider * dataProvider()
Returns the data provider.
bool showLinkButton() const
Determines if the "link feature" button should be shown.
int fieldIdx() const
Access the field index.
bool nextFeature(QgsFeature &f)
void clear()
bool labelOnTop(int idx) const
If this returns true, the widget at the given index will receive its label on the previous line while...
QgsVectorLayer * layer() const
Access the QgsVectorLayer, you are working on.
QString widgetType(int fieldIdx) const
Get the id for the editor widget used to represent the field at the given index.
QgsRelation relation(const QString &id) const
Get access to a relation by its id.
A vector of attributes.
Definition: qgsfeature.h:115
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const
QString readAll()
Represents a vector layer which manages a vector based data sets.
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:97
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:271
QString name() const
Return the name of this element.
int selectedFeatureCount()
The number of features that are selected in this layer.
The QgsTabWidget class is the same as the QTabWidget but with additional methods to temporarily hide/...
Definition: qgstabwidget.h:27
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() const
virtual bool event(QEvent *event)
Manages an editor widget Widget and wrapper share the same parent.
QString attributeDisplayName(int attributeIndex) const
Convenience function that returns the attribute alias if defined or the field name else...
int columnCount() const
EditorLayout layout() const
Get the active layout style for the attribute editor for this layer.
Allows modification of attribute values.
void setShowLinkButton(bool showLinkButton)
Determines if the "link feature" button should be shown.
QString initFunction() const
Get python function for edit form initialization.
int fieldNameIndex(const QString &fieldName) const
Returns the index of a field name or -1 if the field does not exist.
void resetSearch()
Resets the search/filter value of the widget.
A form was embedded as a widget on another form.
void setContentsMargins(int left, int top, int right, int bottom)
QString uiForm() const
Get path to the .ui form.
const T value(const Key &key) const
PythonInitCodeSource initCodeSource() const
Return python code source for edit form initialization (if it shall be loaded from a file...
QString initFilePath() const
Get python external file path for edit form initialization.