QGIS API Documentation  2.15.0-Master (af20121)
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  , mMultiEditUnsavedMessageBarItem( nullptr )
55  , mMultiEditMessageBarItem( nullptr )
56  , mContext( context )
57  , mButtonBox( nullptr )
58  , mSearchButtonBox( nullptr )
59  , mFormNr( sFormCounter++ )
60  , mIsSaving( false )
61  , mPreventFeatureRefresh( false )
62  , mIsSettingFeature( false )
63  , mIsSettingMultiEditFeatures( false )
64  , mUnsavedMultiEditChanges( false )
65  , mEditCommandMessage( tr( "Attributes changed" ) )
66  , mMode( SingleEditMode )
67 {
68  init();
69  initPython();
70  setFeature( feature );
71 
72  // Using attributeAdded() attributeDeleted() are not emitted on all fields changes (e.g. layer fields changed,
73  // joined fields changed) -> use updatedFields() instead
74 #if 0
75  connect( vl, SIGNAL( attributeAdded( int ) ), this, SLOT( onAttributeAdded( int ) ) );
76  connect( vl, SIGNAL( attributeDeleted( int ) ), this, SLOT( onAttributeDeleted( int ) ) );
77 #endif
78  connect( vl, SIGNAL( updatedFields() ), this, SLOT( onUpdatedFields() ) );
79  connect( vl, SIGNAL( beforeAddingExpressionField( QString ) ), this, SLOT( preventFeatureRefresh() ) );
80  connect( vl, SIGNAL( beforeRemovingExpressionField( int ) ), this, SLOT( preventFeatureRefresh() ) );
81  connect( vl, SIGNAL( selectionChanged() ), this, SLOT( layerSelectionChanged() ) );
82 }
83 
85 {
86  cleanPython();
87  qDeleteAll( mInterfaces );
88 }
89 
91 {
92  mButtonBox->hide();
93 
94  // Make sure that changes are taken into account if somebody tries to figure out if there have been some
95  if ( mMode == SingleEditMode )
96  connect( mLayer, SIGNAL( beforeModifiedCheck() ), this, SLOT( save() ) );
97 }
98 
100 {
101  mButtonBox->show();
102 
103  disconnect( mLayer, SIGNAL( beforeModifiedCheck() ), this, SLOT( save() ) );
104 }
105 
107 {
108  disconnect( mButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
109  disconnect( mButtonBox, SIGNAL( rejected() ), this, SLOT( resetValues() ) );
110 }
111 
113 {
114  mInterfaces.append( iface );
115 }
116 
118 {
119  return mFeature.isValid() && mLayer->isEditable();
120 }
121 
123 {
124  if ( mode == mMode )
125  return;
126 
127  if ( mMode == MultiEditMode )
128  {
129  //switching out of multi edit mode triggers a save
130  if ( mUnsavedMultiEditChanges )
131  {
132  // prompt for save
133  int res = QMessageBox::information( this, tr( "Multiedit attributes" ),
134  tr( "Apply changes to edited features?" ), QMessageBox::Yes | QMessageBox::No );
135  if ( res == QMessageBox::Yes )
136  {
137  save();
138  }
139  }
140  clearMultiEditMessages();
141  }
142  mUnsavedMultiEditChanges = false;
143 
144  mMode = mode;
145 
146  if ( mButtonBox->isVisible() && mMode == SingleEditMode )
147  {
148  connect( mLayer, SIGNAL( beforeModifiedCheck() ), this, SLOT( save() ) );
149  }
150  else
151  {
152  disconnect( mLayer, SIGNAL( beforeModifiedCheck() ), this, SLOT( save() ) );
153  }
154 
155  //update all form editor widget modes to match
156  Q_FOREACH ( QgsAttributeFormEditorWidget* w, findChildren< QgsAttributeFormEditorWidget* >() )
157  {
158  switch ( mode )
159  {
162  break;
163 
166  break;
167 
170  break;
171 
174  break;
175  }
176  }
177 
178  switch ( mode )
179  {
181  setFeature( mFeature );
182  mSearchButtonBox->setVisible( false );
183  break;
184 
186  synchronizeEnabledState();
187  mSearchButtonBox->setVisible( false );
188  break;
189 
191  resetMultiEdit( false );
192  synchronizeEnabledState();
193  mSearchButtonBox->setVisible( false );
194  break;
195 
197  mSearchButtonBox->setVisible( true );
198  break;
199  }
200 
201  emit modeChanged( mMode );
202 }
203 
204 void QgsAttributeForm::setIsAddDialog( bool isAddDialog )
205 {
206  setMode( isAddDialog ? AddFeatureMode : SingleEditMode );
207 }
208 
209 void QgsAttributeForm::changeAttribute( const QString& field, const QVariant& value )
210 {
211  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
212  {
213  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
214  if ( eww && eww->field().name() == field )
215  {
216  eww->setValue( value );
217  }
218  }
219 }
220 
222 {
223  mIsSettingFeature = true;
224  mFeature = feature;
225 
226  switch ( mMode )
227  {
228  case SingleEditMode:
229  case AddFeatureMode:
230  {
231  resetValues();
232 
233  synchronizeEnabledState();
234 
235  Q_FOREACH ( QgsAttributeFormInterface* iface, mInterfaces )
236  {
237  iface->featureChanged();
238  }
239  break;
240  }
241  case MultiEditMode:
242  case SearchMode:
243  {
244  //ignore setFeature
245  break;
246  }
247  }
248  mIsSettingFeature = false;
249 }
250 
251 bool QgsAttributeForm::saveEdits()
252 {
253  bool success = true;
254  bool changedLayer = false;
255 
256  QgsFeature updatedFeature = QgsFeature( mFeature );
257 
258  if ( mFeature.isValid() || mMode == AddFeatureMode )
259  {
260  bool doUpdate = false;
261 
262  // An add dialog should perform an action by default
263  // and not only if attributes have "changed"
264  if ( mMode == AddFeatureMode )
265  doUpdate = true;
266 
267  QgsAttributes src = mFeature.attributes();
268  QgsAttributes dst = mFeature.attributes();
269 
270  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
271  {
272  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
273  if ( eww )
274  {
275  QVariant dstVar = dst.at( eww->fieldIdx() );
276  QVariant srcVar = eww->value();
277 
278  // need to check dstVar.isNull() != srcVar.isNull()
279  // otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
280  // be careful- sometimes two null qvariants will be reported as not equal!! (eg different types)
281  bool changed = ( dstVar != srcVar && !dstVar.isNull() && !srcVar.isNull() )
282  || ( dstVar.isNull() != srcVar.isNull() );
283  if ( changed && srcVar.isValid()
284  && !mLayer->editFormConfig()->readOnly( eww->fieldIdx() ) )
285  {
286  dst[eww->fieldIdx()] = srcVar;
287 
288  doUpdate = true;
289  }
290  }
291  }
292 
293  updatedFeature.setAttributes( dst );
294 
295  Q_FOREACH ( QgsAttributeFormInterface* iface, mInterfaces )
296  {
297  if ( !iface->acceptChanges( updatedFeature ) )
298  {
299  doUpdate = false;
300  }
301  }
302 
303  if ( doUpdate )
304  {
305  if ( mMode == AddFeatureMode )
306  {
307  mFeature.setValid( true );
308  mLayer->beginEditCommand( mEditCommandMessage );
309  bool res = mLayer->addFeature( updatedFeature );
310  if ( res )
311  {
312  mFeature.setAttributes( updatedFeature.attributes() );
313  mLayer->endEditCommand();
315  changedLayer = true;
316  }
317  else
318  mLayer->destroyEditCommand();
319  }
320  else
321  {
322  mLayer->beginEditCommand( mEditCommandMessage );
323 
324  int n = 0;
325  for ( int i = 0; i < dst.count(); ++i )
326  {
327  if (( dst.at( i ) == src.at( i ) && dst.at( i ).isNull() == src.at( i ).isNull() ) // If field is not changed...
328  || !dst.at( i ).isValid() // or the widget returns invalid (== do not change)
329  || mLayer->editFormConfig()->readOnly( i ) ) // or the field cannot be edited ...
330  {
331  continue;
332  }
333 
334  QgsDebugMsg( QString( "Updating field %1" ).arg( i ) );
335  QgsDebugMsg( QString( "dst:'%1' (type:%2, isNull:%3, isValid:%4)" )
336  .arg( dst.at( i ).toString(), dst.at( i ).typeName() ).arg( dst.at( i ).isNull() ).arg( dst.at( i ).isValid() ) );
337  QgsDebugMsg( QString( "src:'%1' (type:%2, isNull:%3, isValid:%4)" )
338  .arg( src.at( i ).toString(), src.at( i ).typeName() ).arg( src.at( i ).isNull() ).arg( src.at( i ).isValid() ) );
339 
340  success &= mLayer->changeAttributeValue( mFeature.id(), i, dst.at( i ), src.at( i ) );
341  n++;
342  }
343 
344  if ( success && n > 0 )
345  {
346  mLayer->endEditCommand();
347  mFeature.setAttributes( dst );
348  changedLayer = true;
349  }
350  else
351  {
352  mLayer->destroyEditCommand();
353  }
354  }
355  }
356  }
357 
358  emit featureSaved( updatedFeature );
359 
360  // [MD] Refresh canvas only when absolutely necessary - it interferes with other stuff (#11361).
361  // This code should be revisited - and the signals should be fired (+ layer repainted)
362  // only when actually doing any changes. I am unsure if it is actually a good idea
363  // to call save() whenever some code asks for vector layer's modified status
364  // (which is the case when attribute table is open)
365  if ( changedLayer )
366  mLayer->triggerRepaint();
367 
368  return success;
369 }
370 
371 void QgsAttributeForm::resetMultiEdit( bool promptToSave )
372 {
373  if ( promptToSave )
374  save();
375 
376  mUnsavedMultiEditChanges = false;
378 }
379 
380 void QgsAttributeForm::multiEditMessageClicked( const QString& link )
381 {
382  clearMultiEditMessages();
383  resetMultiEdit( link == "#apply" );
384 }
385 
386 void QgsAttributeForm::filterTriggered()
387 {
388  QString filter = createFilterExpression();
389  emit filterExpressionSet( filter, ReplaceFilter );
391 }
392 
393 void QgsAttributeForm::filterAndTriggered()
394 {
395  QString filter = createFilterExpression();
396  if ( filter.isEmpty() )
397  return;
398 
400  emit filterExpressionSet( filter, FilterAnd );
401 }
402 
403 void QgsAttributeForm::filterOrTriggered()
404 {
405  QString filter = createFilterExpression();
406  if ( filter.isEmpty() )
407  return;
408 
410  emit filterExpressionSet( filter, FilterOr );
411 }
412 
413 void QgsAttributeForm::pushSelectedFeaturesMessage()
414 {
415  int count = mLayer->selectedFeatureCount();
416  if ( count > 0 )
417  {
418  mMessageBar->pushMessage( QString(),
419  tr( "%1 matching %2 selected" ).arg( count )
420  .arg( count == 1 ? tr( "feature" ) : tr( "features" ) ),
422  messageTimeout() );
423  }
424  else
425  {
426  mMessageBar->pushMessage( QString(),
427  tr( "No matching features found" ),
429  messageTimeout() );
430  }
431 }
432 
433 void QgsAttributeForm::runSearchSelect( QgsVectorLayer::SelectBehaviour behaviour )
434 {
435  QString filter = createFilterExpression();
436  if ( filter.isEmpty() )
437  return;
438 
439  mLayer->selectByExpression( filter, behaviour );
440  pushSelectedFeaturesMessage();
442 }
443 
444 void QgsAttributeForm::searchSetSelection()
445 {
446  runSearchSelect( QgsVectorLayer::SetSelection );
447 }
448 
449 void QgsAttributeForm::searchAddToSelection()
450 {
451  runSearchSelect( QgsVectorLayer::AddToSelection );
452 }
453 
454 void QgsAttributeForm::searchRemoveFromSelection()
455 {
456  runSearchSelect( QgsVectorLayer::RemoveFromSelection );
457 }
458 
459 void QgsAttributeForm::searchIntersectSelection()
460 {
461  runSearchSelect( QgsVectorLayer::IntersectSelection );
462 }
463 
464 bool QgsAttributeForm::saveMultiEdits()
465 {
466  //find changed attributes
467  QgsAttributeMap newAttributeValues;
469  for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
470  {
472  if ( !w->hasChanged() )
473  continue;
474 
475  if ( !w->currentValue().isValid() // if the widget returns invalid (== do not change)
476  || mLayer->editFormConfig()->readOnly( wIt.key() ) ) // or the field cannot be edited ...
477  {
478  continue;
479  }
480 
481  // let editor know we've accepted the changes
482  w->changesCommitted();
483 
484  newAttributeValues.insert( wIt.key(), w->currentValue() );
485  }
486 
487  if ( newAttributeValues.isEmpty() )
488  {
489  //nothing to change
490  return true;
491  }
492 
493 #if 0
494  // prompt for save
495  int res = QMessageBox::information( this, tr( "Multiedit attributes" ),
496  tr( "Edits will be applied to all selected features" ), QMessageBox::Ok | QMessageBox::Cancel );
497  if ( res != QMessageBox::Ok )
498  {
499  resetMultiEdit();
500  return false;
501  }
502 #endif
503 
504  mLayer->beginEditCommand( tr( "Updated multiple feature attributes" ) );
505 
506  bool success = true;
507 
508  Q_FOREACH ( QgsFeatureId fid, mMultiEditFeatureIds )
509  {
510  QgsAttributeMap::const_iterator aIt = newAttributeValues.constBegin();
511  for ( ; aIt != newAttributeValues.constEnd(); ++aIt )
512  {
513  success &= mLayer->changeAttributeValue( fid, aIt.key(), aIt.value() );
514  }
515  }
516 
517  clearMultiEditMessages();
518  if ( success )
519  {
520  mLayer->endEditCommand();
521  mLayer->triggerRepaint();
522  mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Attribute changes for multiple features applied" ), QgsMessageBar::SUCCESS, messageTimeout() );
523  }
524  else
525  {
526  mLayer->destroyEditCommand();
527  mMultiEditMessageBarItem = new QgsMessageBarItem( tr( "Changes could not be applied" ), QgsMessageBar::WARNING, messageTimeout() );
528  }
529 
530  if ( !mButtonBox->isVisible() )
531  mMessageBar->pushItem( mMultiEditMessageBarItem );
532  return success;
533 }
534 
536 {
537  if ( mIsSaving )
538  return true;
539 
540  mIsSaving = true;
541 
542  bool success = true;
543 
544  emit beforeSave( success );
545 
546  // Somebody wants to prevent this form from saving
547  if ( !success )
548  return false;
549 
550  switch ( mMode )
551  {
552  case SingleEditMode:
553  case AddFeatureMode:
554  case SearchMode:
555  success = saveEdits();
556  break;
557 
558  case MultiEditMode:
559  success = saveMultiEdits();
560  break;
561  }
562 
563  mIsSaving = false;
564  mUnsavedMultiEditChanges = false;
565 
566  return success;
567 }
568 
570 {
571  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
572  {
573  ww->setFeature( mFeature );
574  }
575 }
576 
578 {
579  Q_FOREACH ( QgsAttributeFormEditorWidget* w, findChildren< QgsAttributeFormEditorWidget* >() )
580  {
581  w->resetSearch();
582  }
583 }
584 
585 void QgsAttributeForm::clearMultiEditMessages()
586 {
587  if ( mMultiEditUnsavedMessageBarItem )
588  {
589  if ( !mButtonBox->isVisible() )
590  mMessageBar->popWidget( mMultiEditUnsavedMessageBarItem );
591  mMultiEditUnsavedMessageBarItem = nullptr;
592  }
593  if ( mMultiEditMessageBarItem )
594  {
595  if ( !mButtonBox->isVisible() )
596  mMessageBar->popWidget( mMultiEditMessageBarItem );
597  mMultiEditMessageBarItem = nullptr;
598  }
599 }
600 
601 QString QgsAttributeForm::createFilterExpression() const
602 {
603  QStringList filters;
604  Q_FOREACH ( QgsAttributeFormEditorWidget* w, findChildren< QgsAttributeFormEditorWidget* >() )
605  {
606  QString filter = w->currentFilterExpression();
607  if ( !filter.isEmpty() )
608  filters << filter;
609  }
610 
611  if ( filters.isEmpty() )
612  return QString();
613 
614  QString filter = filters.join( ") AND (" ).prepend( '(' ).append( ')' );
615  return filter;
616 }
617 
618 void QgsAttributeForm::onAttributeChanged( const QVariant& value )
619 {
620  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( sender() );
621 
622  Q_ASSERT( eww );
623 
624  switch ( mMode )
625  {
626  case SingleEditMode:
627  case AddFeatureMode:
628  {
629  // don't emit signal if it was triggered by a feature change
630  if ( !mIsSettingFeature )
631  {
632  emit attributeChanged( eww->field().name(), value );
633  }
634  break;
635  }
636  case MultiEditMode:
637  {
638  if ( !mIsSettingMultiEditFeatures )
639  {
640  mUnsavedMultiEditChanges = true;
641 
642  QLabel *msgLabel = new QLabel( tr( "Unsaved multiedit changes: <a href=\"#apply\">apply changes</a> or <a href=\"#reset\">reset changes</a>." ), mMessageBar );
643  msgLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
644  msgLabel->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
645  connect( msgLabel, SIGNAL( linkActivated( QString ) ), this, SLOT( multiEditMessageClicked( QString ) ) );
646  clearMultiEditMessages();
647 
648  mMultiEditUnsavedMessageBarItem = new QgsMessageBarItem( msgLabel, QgsMessageBar::WARNING );
649  if ( !mButtonBox->isVisible() )
650  mMessageBar->pushItem( mMultiEditUnsavedMessageBarItem );
651  }
652  break;
653  }
654  case SearchMode:
655  //nothing to do
656  break;
657  }
658 }
659 
660 void QgsAttributeForm::onAttributeAdded( int idx )
661 {
662  mPreventFeatureRefresh = false;
663  if ( mFeature.isValid() )
664  {
665  QgsAttributes attrs = mFeature.attributes();
666  attrs.insert( idx, QVariant( layer()->fields().at( idx ).type() ) );
667  mFeature.setFields( layer()->fields() );
668  mFeature.setAttributes( attrs );
669  }
670  init();
671  setFeature( mFeature );
672 }
673 
674 void QgsAttributeForm::onAttributeDeleted( int idx )
675 {
676  mPreventFeatureRefresh = false;
677  if ( mFeature.isValid() )
678  {
679  QgsAttributes attrs = mFeature.attributes();
680  attrs.remove( idx );
681  mFeature.setFields( layer()->fields() );
682  mFeature.setAttributes( attrs );
683  }
684  init();
685  setFeature( mFeature );
686 }
687 
688 void QgsAttributeForm::onUpdatedFields()
689 {
690  mPreventFeatureRefresh = false;
691  if ( mFeature.isValid() )
692  {
693  QgsAttributes attrs( layer()->fields().size() );
694  for ( int i = 0; i < layer()->fields().size(); i++ )
695  {
696  int idx = mFeature.fields()->indexFromName( layer()->fields().at( i ).name() );
697  if ( idx != -1 )
698  {
699  attrs[i] = mFeature.attributes().at( idx );
700  if ( mFeature.attributes().at( idx ).type() != layer()->fields().at( i ).type() )
701  {
702  attrs[i].convert( layer()->fields().at( i ).type() );
703  }
704  }
705  else
706  {
707  attrs[i] = QVariant( layer()->fields().at( i ).type() );
708  }
709  }
710  mFeature.setFields( layer()->fields() );
711  mFeature.setAttributes( attrs );
712  }
713  init();
714  setFeature( mFeature );
715 }
716 
717 void QgsAttributeForm::preventFeatureRefresh()
718 {
719  mPreventFeatureRefresh = true;
720 }
721 
723 {
724  if ( mPreventFeatureRefresh || mLayer->isEditable() || !mFeature.isValid() )
725  return;
726 
727  // reload feature if layer changed although not editable
728  // (datasource probably changed bypassing QgsVectorLayer)
729  if ( !mLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeature.id() ) ).nextFeature( mFeature ) )
730  return;
731 
732  init();
733  setFeature( mFeature );
734 }
735 
736 void QgsAttributeForm::synchronizeEnabledState()
737 {
738  bool isEditable = ( mFeature.isValid()
739  || mMode == AddFeatureMode
740  || mMode == MultiEditMode ) && mLayer->isEditable();
741 
742  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
743  {
744  bool fieldEditable = true;
745  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
746  if ( eww )
747  {
748  fieldEditable = !mLayer->editFormConfig()->readOnly( eww->fieldIdx() ) &&
750  FID_IS_NEW( mFeature.id() ) );
751  }
752  ww->setEnabled( isEditable && fieldEditable );
753  }
754 
755  QPushButton* okButton = mButtonBox->button( QDialogButtonBox::Ok );
756  if ( okButton )
757  okButton->setEnabled( isEditable );
758 }
759 
760 void QgsAttributeForm::init()
761 {
762  QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
763 
764  // Cleanup of any previously shown widget, we start from scratch
765  QWidget* formWidget = nullptr;
766 
767  bool buttonBoxVisible = true;
768  // Cleanup button box but preserve visibility
769  if ( mButtonBox )
770  {
771  buttonBoxVisible = mButtonBox->isVisible();
772  delete mButtonBox;
773  mButtonBox = nullptr;
774  }
775 
776  if ( mSearchButtonBox )
777  {
778  delete mSearchButtonBox;
779  mSearchButtonBox = nullptr;
780  }
781 
782  qDeleteAll( mWidgets );
783  mWidgets.clear();
784 
785  while ( QWidget* w = this->findChild<QWidget*>() )
786  {
787  delete w;
788  }
789  delete layout();
790 
791  QVBoxLayout* vl = new QVBoxLayout();
792  vl->setMargin( 0 );
793  vl->setContentsMargins( 0, 0, 0, 0 );
794  mMessageBar = new QgsMessageBar( this );
795  mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
796  vl->addWidget( mMessageBar );
797  setLayout( vl );
798 
799  // Get a layout
800  QGridLayout* layout = new QGridLayout();
801  QWidget* container = new QWidget();
802  container->setLayout( layout );
803  vl->addWidget( container );
804 
805  mFormEditorWidgets.clear();
806 
807  // a bar to warn the user with non-blocking messages
808  setContentsMargins( 0, 0, 0, 0 );
809 
810  // Try to load Ui-File for layout
811  if ( mLayer->editFormConfig()->layout() == QgsEditFormConfig::UiFileLayout && !mLayer->editFormConfig()->uiForm().isEmpty() )
812  {
813  QFile file( mLayer->editFormConfig()->uiForm() );
814 
815  if ( file.open( QFile::ReadOnly ) )
816  {
817  QUiLoader loader;
818 
819  QFileInfo fi( mLayer->editFormConfig()->uiForm() );
820  loader.setWorkingDirectory( fi.dir() );
821  formWidget = loader.load( &file, this );
822  formWidget->setWindowFlags( Qt::Widget );
823  layout->addWidget( formWidget );
824  formWidget->show();
825  file.close();
826  mButtonBox = findChild<QDialogButtonBox*>();
827  createWrappers();
828 
829  formWidget->installEventFilter( this );
830  }
831  }
832 
833  // Tab layout
834  if ( !formWidget && mLayer->editFormConfig()->layout() == QgsEditFormConfig::TabLayout )
835  {
836  QTabWidget* tabWidget = new QTabWidget();
837  layout->addWidget( tabWidget );
838 
839  Q_FOREACH ( QgsAttributeEditorElement* widgDef, mLayer->editFormConfig()->tabs() )
840  {
841  QWidget* tabPage = new QWidget( tabWidget );
842 
843  tabWidget->addTab( tabPage, widgDef->name() );
844  QGridLayout* tabPageLayout = new QGridLayout();
845  tabPage->setLayout( tabPageLayout );
846 
848  {
849  QgsAttributeEditorContainer* containerDef = dynamic_cast<QgsAttributeEditorContainer*>( widgDef );
850  if ( !containerDef )
851  continue;
852 
853  containerDef->setIsGroupBox( false ); // Toplevel widgets are tabs not groupboxes
854  WidgetInfo widgetInfo = createWidgetFromDef( widgDef, tabPage, mLayer, mContext );
855  tabPageLayout->addWidget( widgetInfo.widget );
856  }
857  else
858  {
859  QgsDebugMsg( "No support for fields in attribute editor on top level" );
860  }
861  }
862  formWidget = tabWidget;
863  }
864 
865  // Autogenerate Layout
866  // If there is still no layout loaded (defined as autogenerate or other methods failed)
867  if ( !formWidget )
868  {
869  formWidget = new QWidget( this );
870  QGridLayout* gridLayout = new QGridLayout( formWidget );
871  formWidget->setLayout( gridLayout );
872 
873  // put the form into a scroll area to nicely handle cases with lots of attributes
874 
875  QScrollArea* scrollArea = new QScrollArea( this );
876  scrollArea->setWidget( formWidget );
877  scrollArea->setWidgetResizable( true );
878  scrollArea->setFrameShape( QFrame::NoFrame );
879  scrollArea->setFrameShadow( QFrame::Plain );
880  scrollArea->setFocusProxy( this );
881  layout->addWidget( scrollArea );
882 
883  int row = 0;
884  Q_FOREACH ( const QgsField& field, mLayer->fields().toList() )
885  {
886  int idx = mLayer->fieldNameIndex( field.name() );
887  if ( idx < 0 )
888  continue;
889 
890  //show attribute alias if available
891  QString fieldName = mLayer->attributeDisplayName( idx );
892 
893  const QString widgetType = mLayer->editFormConfig()->widgetType( idx );
894 
895  if ( widgetType == "Hidden" )
896  continue;
897 
898  const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig()->widgetConfig( idx );
899  bool labelOnTop = mLayer->editFormConfig()->labelOnTop( idx );
900 
901  // This will also create the widget
902  QWidget *l = new QLabel( fieldName );
903  QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, idx, widgetConfig, nullptr, this, mContext );
904 
905  QWidget* w = nullptr;
906  if ( eww )
907  {
908  QgsAttributeFormEditorWidget* formWidget = new QgsAttributeFormEditorWidget( eww, this );
909  w = formWidget;
910  mFormEditorWidgets.insert( idx, formWidget );
911  formWidget->createSearchWidgetWrappers( widgetType, idx, widgetConfig, mContext );
912  }
913  else
914  {
915  w = new QLabel( QString( "<p style=\"color: red; font-style: italic;\">Failed to create widget with type '%1'</p>" ).arg( widgetType ) );
916  }
917 
918  if ( eww )
919  addWidgetWrapper( eww );
920 
921  if ( labelOnTop )
922  {
923  gridLayout->addWidget( l, row++, 0, 1, 2 );
924  gridLayout->addWidget( w, row++, 0, 1, 2 );
925  }
926  else
927  {
928  gridLayout->addWidget( l, row, 0 );
929  gridLayout->addWidget( w, row++, 1 );
930  }
931  }
932 
933  Q_FOREACH ( const QgsRelation& rel, QgsProject::instance()->relationManager()->referencedRelations( mLayer ) )
934  {
935  QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, rel, nullptr, this );
936  QgsEditorWidgetConfig cfg = mLayer->editFormConfig()->widgetConfig( rel.id() );
937  rww->setConfig( cfg );
938  rww->setContext( mContext );
939  gridLayout->addWidget( rww->widget(), row++, 0, 1, 2 );
940  mWidgets.append( rww );
941  }
942 
943  if ( QgsProject::instance()->relationManager()->referencedRelations( mLayer ).isEmpty() )
944  {
945  QSpacerItem *spacerItem = new QSpacerItem( 20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding );
946  gridLayout->addItem( spacerItem, row, 0 );
947  gridLayout->setRowStretch( row, 1 );
948  row++;
949  }
950  }
951 
952  if ( !mButtonBox )
953  {
954  mButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
955  mButtonBox->setObjectName( "buttonBox" );
956  layout->addWidget( mButtonBox );
957  }
958  mButtonBox->setVisible( buttonBoxVisible );
959 
960  if ( !mSearchButtonBox )
961  {
962  mSearchButtonBox = new QWidget();
963  QHBoxLayout* boxLayout = new QHBoxLayout();
964  boxLayout->setMargin( 0 );
965  boxLayout->setContentsMargins( 0, 0, 0, 0 );
966  mSearchButtonBox->setLayout( boxLayout );
967  mSearchButtonBox->setObjectName( "searchButtonBox" );
968 
969  QPushButton* clearButton = new QPushButton( tr( "Reset form" ), mSearchButtonBox );
970  connect( clearButton, SIGNAL( clicked( bool ) ), this, SLOT( resetSearch() ) );
971  boxLayout->addWidget( clearButton );
972  boxLayout->addStretch( 1 );
973 
974  QToolButton* selectButton = new QToolButton();
975  selectButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
976  selectButton->setText( tr( "Select features" ) );
977  selectButton->setPopupMode( QToolButton::MenuButtonPopup );
978  connect( selectButton, SIGNAL( clicked( bool ) ), this, SLOT( searchSetSelection() ) );
979  QMenu* selectMenu = new QMenu( selectButton );
980  QAction* selectAction = new QAction( tr( "Select features" ), selectMenu );
981  connect( selectAction, SIGNAL( triggered( bool ) ), this, SLOT( searchSetSelection() ) );
982  selectMenu->addAction( selectAction );
983  QAction* addSelectAction = new QAction( tr( "Add to current selection" ), selectMenu );
984  connect( addSelectAction, SIGNAL( triggered( bool ) ), this, SLOT( searchAddToSelection() ) );
985  selectMenu->addAction( addSelectAction );
986  QAction* filterSelectAction = new QAction( tr( "Filter current selection" ), selectMenu );
987  connect( filterSelectAction, SIGNAL( triggered( bool ) ), this, SLOT( searchIntersectSelection() ) );
988  selectMenu->addAction( filterSelectAction );
989  QAction* deselectAction = new QAction( tr( "Remove from current selection" ), selectMenu );
990  connect( deselectAction, SIGNAL( triggered( bool ) ), this, SLOT( searchRemoveFromSelection() ) );
991  selectMenu->addAction( deselectAction );
992  selectButton->setMenu( selectMenu );
993  boxLayout->addWidget( selectButton );
994 
995  QToolButton* filterButton = new QToolButton();
996  filterButton->setText( tr( "Filter features" ) );
997  filterButton->setPopupMode( QToolButton::MenuButtonPopup );
998  filterButton->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
999  connect( filterButton, SIGNAL( clicked( bool ) ), this, SLOT( filterTriggered() ) );
1000  QMenu* filterMenu = new QMenu( filterButton );
1001  QAction* filterAndAction = new QAction( tr( "Filter within (\"AND\")" ), filterMenu );
1002  connect( filterAndAction, SIGNAL( triggered( bool ) ), this, SLOT( filterAndTriggered() ) );
1003  filterMenu->addAction( filterAndAction );
1004  QAction* filterOrAction = new QAction( tr( "Extend filter (\"OR\")" ), filterMenu );
1005  connect( filterOrAction, SIGNAL( triggered( bool ) ), this, SLOT( filterOrTriggered() ) );
1006  filterMenu->addAction( filterOrAction );
1007  filterButton->setMenu( filterMenu );
1008  boxLayout->addWidget( filterButton );
1009 
1010  layout->addWidget( mSearchButtonBox );
1011  }
1012  mSearchButtonBox->setVisible( mMode == SearchMode );
1013 
1014  connectWrappers();
1015 
1016  connect( mButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
1017  connect( mButtonBox, SIGNAL( rejected() ), this, SLOT( resetValues() ) );
1018 
1019  connect( mLayer, SIGNAL( editingStarted() ), this, SLOT( synchronizeEnabledState() ) );
1020  connect( mLayer, SIGNAL( editingStopped() ), this, SLOT( synchronizeEnabledState() ) );
1021 
1022  Q_FOREACH ( QgsAttributeFormInterface* iface, mInterfaces )
1023  {
1024  iface->initForm();
1025  }
1027 }
1028 
1029 void QgsAttributeForm::cleanPython()
1030 {
1031  if ( !mPyFormVarName.isNull() )
1032  {
1033  QString expr = QString( "if locals().has_key('%1'): del %1\n" ).arg( mPyFormVarName );
1034  QgsPythonRunner::run( expr );
1035  }
1036 }
1037 
1038 void QgsAttributeForm::initPython()
1039 {
1040  cleanPython();
1041 
1042  // Init Python, if init function is not empty and the combo indicates
1043  // the source for the function code
1044  if ( !mLayer->editFormConfig()->initFunction().isEmpty()
1046  {
1047 
1048  QString initFunction = mLayer->editFormConfig()->initFunction();
1049  QString initFilePath = mLayer->editFormConfig()->initFilePath();
1050  QString initCode;
1051 
1052  switch ( mLayer->editFormConfig()->initCodeSource() )
1053  {
1055  if ( ! initFilePath.isEmpty() )
1056  {
1057  QFile inputFile( initFilePath );
1058 
1059  if ( inputFile.open( QFile::ReadOnly ) )
1060  {
1061  // Read it into a string
1062  QTextStream inf( &inputFile );
1063  initCode = inf.readAll();
1064  inputFile.close();
1065  }
1066  else // The file couldn't be opened
1067  {
1068  QgsLogger::warning( QString( "The external python file path %1 could not be opened!" ).arg( initFilePath ) );
1069  }
1070  }
1071  else
1072  {
1073  QgsLogger::warning( QString( "The external python file path is empty!" ) );
1074  }
1075  break;
1076 
1078  initCode = mLayer->editFormConfig()->initCode();
1079  if ( initCode.isEmpty() )
1080  {
1081  QgsLogger::warning( QString( "The python code provided in the dialog is empty!" ) );
1082  }
1083  break;
1084 
1087  default:
1088  // Nothing to do: the function code should be already in the environment
1089  break;
1090  }
1091 
1092  // If we have a function code, run it
1093  if ( ! initCode.isEmpty() )
1094  {
1095  QgsPythonRunner::run( initCode );
1096  }
1097 
1098  QgsPythonRunner::run( "import inspect" );
1099  QString numArgs;
1100 
1101  // Check for eval result
1102  if ( QgsPythonRunner::eval( QString( "len(inspect.getargspec(%1)[0])" ).arg( initFunction ), numArgs ) )
1103  {
1104  static int sFormId = 0;
1105  mPyFormVarName = QString( "_qgis_featureform_%1_%2" ).arg( mFormNr ).arg( sFormId++ );
1106 
1107  QString form = QString( "%1 = sip.wrapinstance( %2, qgis.gui.QgsAttributeForm )" )
1108  .arg( mPyFormVarName )
1109  .arg(( unsigned long ) this );
1110 
1111  QgsPythonRunner::run( form );
1112 
1113  QgsDebugMsg( QString( "running featureForm init: %1" ).arg( mPyFormVarName ) );
1114 
1115  // Legacy
1116  if ( numArgs == "3" )
1117  {
1118  addInterface( new QgsAttributeFormLegacyInterface( initFunction, mPyFormVarName, this ) );
1119  }
1120  else
1121  {
1122  // If we get here, it means that the function doesn't accept three arguments
1123  QMessageBox msgBox;
1124  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 ) );
1125  msgBox.exec();
1126 #if 0
1127  QString expr = QString( "%1(%2)" )
1128  .arg( mLayer->editFormInit() )
1129  .arg( mPyFormVarName );
1130  QgsAttributeFormInterface* iface = QgsPythonRunner::evalToSipObject<QgsAttributeFormInterface*>( expr, "QgsAttributeFormInterface" );
1131  if ( iface )
1132  addInterface( iface );
1133 #endif
1134  }
1135  }
1136  else
1137  {
1138  // If we get here, it means that inspect couldn't find the function
1139  QMessageBox msgBox;
1140  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 ) );
1141  msgBox.exec();
1142  }
1143  }
1144 }
1145 
1146 QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAttributeEditorElement* widgetDef, QWidget* parent, QgsVectorLayer* vl, QgsAttributeEditorContext& context )
1147 {
1148  WidgetInfo newWidgetInfo;
1149 
1150  switch ( widgetDef->type() )
1151  {
1153  {
1154  const QgsAttributeEditorField* fieldDef = dynamic_cast<const QgsAttributeEditorField*>( widgetDef );
1155  if ( !fieldDef )
1156  break;
1157 
1158  int fldIdx = vl->fieldNameIndex( fieldDef->name() );
1159  if ( fldIdx < vl->fields().count() && fldIdx >= 0 )
1160  {
1161  const QString widgetType = mLayer->editFormConfig()->widgetType( fldIdx );
1162  const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig()->widgetConfig( fldIdx );
1163 
1164  QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, fldIdx, widgetConfig, nullptr, this, mContext );
1166  mFormEditorWidgets.insert( fldIdx, w );
1167 
1168  w->createSearchWidgetWrappers( widgetType, fldIdx, widgetConfig, mContext );
1169 
1170  newWidgetInfo.widget = w;
1171  addWidgetWrapper( eww );
1172 
1173  newWidgetInfo.widget->setObjectName( mLayer->fields().at( fldIdx ).name() );
1174  }
1175 
1176  newWidgetInfo.labelOnTop = mLayer->editFormConfig()->labelOnTop( fieldDef->idx() );
1177  newWidgetInfo.labelText = mLayer->attributeDisplayName( fieldDef->idx() );
1178 
1179  break;
1180  }
1181 
1183  {
1184  const QgsAttributeEditorRelation* relDef = dynamic_cast<const QgsAttributeEditorRelation*>( widgetDef );
1185 
1186  QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, relDef->relation(), nullptr, this );
1187  QgsEditorWidgetConfig cfg = mLayer->editFormConfig()->widgetConfig( relDef->relation().id() );
1188  rww->setConfig( cfg );
1189  rww->setContext( context );
1190  newWidgetInfo.widget = rww->widget();
1191  mWidgets.append( rww );
1192  newWidgetInfo.labelText = QString::null;
1193  newWidgetInfo.labelOnTop = true;
1194  break;
1195  }
1196 
1198  {
1199  const QgsAttributeEditorContainer* container = dynamic_cast<const QgsAttributeEditorContainer*>( widgetDef );
1200  if ( !container )
1201  break;
1202 
1203  int columnCount = container->columnCount();
1204 
1205  if ( columnCount <= 0 )
1206  columnCount = 1;
1207 
1208  QWidget* myContainer;
1209  if ( container->isGroupBox() )
1210  {
1211  QGroupBox* groupBox = new QGroupBox( parent );
1212  groupBox->setTitle( container->name() );
1213  myContainer = groupBox;
1214  newWidgetInfo.widget = myContainer;
1215  }
1216  else
1217  {
1218  QScrollArea *scrollArea = new QScrollArea( parent );
1219 
1220  myContainer = new QWidget( scrollArea );
1221 
1222  scrollArea->setWidget( myContainer );
1223  scrollArea->setWidgetResizable( true );
1224  scrollArea->setFrameShape( QFrame::NoFrame );
1225 
1226  newWidgetInfo.widget = scrollArea;
1227  }
1228 
1229  QGridLayout* gbLayout = new QGridLayout();
1230  myContainer->setLayout( gbLayout );
1231 
1232  int row = 0;
1233  int column = 0;
1234 
1236 
1237  Q_FOREACH ( QgsAttributeEditorElement* childDef, children )
1238  {
1239  WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context );
1240 
1241  if ( widgetInfo.labelText.isNull() )
1242  {
1243  gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 );
1244  column += 2;
1245  }
1246  else
1247  {
1248  QLabel* mypLabel = new QLabel( widgetInfo.labelText );
1249  if ( columnCount > 1 && !widgetInfo.labelOnTop )
1250  {
1251  mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
1252  }
1253 
1254  if ( widgetInfo.labelOnTop )
1255  {
1256  QVBoxLayout* c = new QVBoxLayout();
1257  mypLabel->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
1258  c->layout()->addWidget( mypLabel );
1259  c->layout()->addWidget( widgetInfo.widget );
1260  gbLayout->addLayout( c, row, column, 1, 2 );
1261  column += 2;
1262  }
1263  else
1264  {
1265  gbLayout->addWidget( mypLabel, row, column++ );
1266  gbLayout->addWidget( widgetInfo.widget, row, column++ );
1267  }
1268  }
1269 
1270  if ( column >= columnCount * 2 )
1271  {
1272  column = 0;
1273  row += 1;
1274  }
1275  }
1276  QWidget* spacer = new QWidget();
1277  spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Preferred );
1278  gbLayout->addWidget( spacer, ++row, 0 );
1279  gbLayout->setRowStretch( row, 1 );
1280 
1281  newWidgetInfo.labelText = QString::null;
1282  newWidgetInfo.labelOnTop = true;
1283  break;
1284  }
1285 
1286  default:
1287  QgsDebugMsg( "Unknown attribute editor widget type encountered..." );
1288  break;
1289  }
1290 
1291  return newWidgetInfo;
1292 }
1293 
1294 void QgsAttributeForm::addWidgetWrapper( QgsEditorWidgetWrapper* eww )
1295 {
1296  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
1297  {
1298  QgsEditorWidgetWrapper* meww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
1299  if ( meww )
1300  {
1301  if ( meww->field() == eww->field() )
1302  {
1303  connect( meww, SIGNAL( valueChanged( QVariant ) ), eww, SLOT( setValue( QVariant ) ) );
1304  connect( eww, SIGNAL( valueChanged( QVariant ) ), meww, SLOT( setValue( QVariant ) ) );
1305  break;
1306  }
1307  }
1308  }
1309 
1310  mWidgets.append( eww );
1311 }
1312 
1313 void QgsAttributeForm::createWrappers()
1314 {
1315  QList<QWidget*> myWidgets = findChildren<QWidget*>();
1316  const QList<QgsField> fields = mLayer->fields().toList();
1317 
1318  Q_FOREACH ( QWidget* myWidget, myWidgets )
1319  {
1320  // Check the widget's properties for a relation definition
1321  QVariant vRel = myWidget->property( "qgisRelation" );
1322  if ( vRel.isValid() )
1323  {
1325  QgsRelation relation = relMgr->relation( vRel.toString() );
1326  if ( relation.isValid() )
1327  {
1328  QgsRelationWidgetWrapper* rww = new QgsRelationWidgetWrapper( mLayer, relation, myWidget, this );
1329  rww->setConfig( mLayer->editFormConfig()->widgetConfig( relation.id() ) );
1330  rww->setContext( mContext );
1331  rww->widget(); // Will initialize the widget
1332  mWidgets.append( rww );
1333  }
1334  }
1335  else
1336  {
1337  Q_FOREACH ( const QgsField& field, fields )
1338  {
1339  if ( field.name() == myWidget->objectName() )
1340  {
1341  const QString widgetType = mLayer->editFormConfig()->widgetType( field.name() );
1342  const QgsEditorWidgetConfig widgetConfig = mLayer->editFormConfig()->widgetConfig( field.name() );
1343  int idx = mLayer->fieldNameIndex( field.name() );
1344 
1345  QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, idx, widgetConfig, myWidget, this, mContext );
1346  addWidgetWrapper( eww );
1347  }
1348  }
1349  }
1350  }
1351 }
1352 
1353 void QgsAttributeForm::connectWrappers()
1354 {
1355  bool isFirstEww = true;
1356 
1357  Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
1358  {
1359  QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
1360 
1361  if ( eww )
1362  {
1363  if ( isFirstEww )
1364  {
1365  setFocusProxy( eww->widget() );
1366  isFirstEww = false;
1367  }
1368 
1369  connect( eww, SIGNAL( valueChanged( const QVariant& ) ), this, SLOT( onAttributeChanged( const QVariant& ) ) );
1370  }
1371  }
1372 }
1373 
1374 
1376 {
1377  Q_UNUSED( object )
1378 
1379  if ( e->type() == QEvent::KeyPress )
1380  {
1381  QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>( e );
1382  if ( keyEvent && keyEvent->key() == Qt::Key_Escape )
1383  {
1384  // Re-emit to this form so it will be forwarded to parent
1385  event( e );
1386  return true;
1387  }
1388  }
1389 
1390  return false;
1391 }
1392 
1393 void QgsAttributeForm::scanForEqualAttributes( QgsFeatureIterator& fit, QSet< int >& mixedValueFields, QHash< int, QVariant >& fieldSharedValues ) const
1394 {
1395  mixedValueFields.clear();
1396  fieldSharedValues.clear();
1397 
1398  QgsFeature f;
1399  bool first = true;
1400  while ( fit.nextFeature( f ) )
1401  {
1402  for ( int i = 0; i < mLayer->fields().count(); ++i )
1403  {
1404  if ( mixedValueFields.contains( i ) )
1405  continue;
1406 
1407  if ( first )
1408  {
1409  fieldSharedValues[i] = f.attribute( i );
1410  }
1411  else
1412  {
1413  if ( fieldSharedValues.value( i ) != f.attribute( i ) )
1414  {
1415  fieldSharedValues.remove( i );
1416  mixedValueFields.insert( i );
1417  }
1418  }
1419  }
1420  first = false;
1421 
1422  if ( mixedValueFields.count() == mLayer->fields().count() )
1423  {
1424  // all attributes are mixed, no need to keep scanning
1425  break;
1426  }
1427  }
1428 }
1429 
1430 
1431 void QgsAttributeForm::layerSelectionChanged()
1432 {
1433  switch ( mMode )
1434  {
1435  case SingleEditMode:
1436  case AddFeatureMode:
1437  case SearchMode:
1438  break;
1439 
1440  case MultiEditMode:
1441  resetMultiEdit( true );
1442  break;
1443  }
1444 }
1445 
1447 {
1448  mIsSettingMultiEditFeatures = true;
1449  mMultiEditFeatureIds = fids;
1450 
1451  if ( fids.isEmpty() )
1452  {
1453  // no selected features
1455  for ( ; wIt != mFormEditorWidgets.constEnd(); ++ wIt )
1456  {
1457  wIt.value()->initialize( QVariant() );
1458  }
1459  mIsSettingMultiEditFeatures = false;
1460  return;
1461  }
1462 
1463  QgsFeatureIterator fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFids( fids ) );
1464 
1465  // Scan through all features to determine which attributes are initially the same
1466  QSet< int > mixedValueFields;
1467  QHash< int, QVariant > fieldSharedValues;
1468  scanForEqualAttributes( fit, mixedValueFields, fieldSharedValues );
1469 
1470  // also fetch just first feature
1471  fit = mLayer->getFeatures( QgsFeatureRequest().setFilterFid( *fids.constBegin() ) );
1472  QgsFeature firstFeature;
1473  fit.nextFeature( firstFeature );
1474 
1475  Q_FOREACH ( int field, mixedValueFields )
1476  {
1477  if ( QgsAttributeFormEditorWidget* w = mFormEditorWidgets.value( field, nullptr ) )
1478  {
1479  w->initialize( firstFeature.attribute( field ), true );
1480  }
1481  }
1482  QHash< int, QVariant >::const_iterator sharedValueIt = fieldSharedValues.constBegin();
1483  for ( ; sharedValueIt != fieldSharedValues.constEnd(); ++sharedValueIt )
1484  {
1485  if ( QgsAttributeFormEditorWidget* w = mFormEditorWidgets.value( sharedValueIt.key(), nullptr ) )
1486  {
1487  w->initialize( sharedValueIt.value(), false );
1488  }
1489  }
1490  mIsSettingMultiEditFeatures = false;
1491 }
1492 
1493 int QgsAttributeForm::messageTimeout()
1494 {
1495  QSettings settings;
1496  return settings.value( "/qgis/messageTimeout", 5 ).toInt();
1497 }
QLayout * layout() const
QList< QgsField > toList() const
Utility function to return a list of QgsField instances.
Definition: qgsfield.cpp:429
QgsFeatureId id() const
Get the feature ID for this feature.
Definition: qgsfeature.cpp:65
Load the python code from an external file.
Use the python code available in the python environment.
void resetValues()
Sets all values to the values of the current feature.
virtual void setEnabled(bool enabled)
Is used to enable or disable the edit functionality of the managed widget.
void resetSearch()
Resets the search/filter form values.
void clear()
Wrapper for iterator of features from vector data provider or vector layer.
void pushMessage(const QString &text, MessageLevel level=INFO, int duration=0)
convenience method for pushing a message to the bar
Definition: qgsmessagebar.h:90
bool isValid() const
Returns the validity of this relation.
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.
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.
virtual bool isGroupBox() const
Returns if this container is going to be rendered as a group box.
PythonInitCodeSource initCodeSource() const
Return python code source for edit form initialization (if it shall be loaded from a file...
Q_DECL_DEPRECATED void accept()
Alias for save()
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:199
void beginEditCommand(const QString &text)
Create edit command for undo/redo operations.
void addWidget(QWidget *widget, int row, int column, QFlags< Qt::AlignmentFlag > alignment)
void hideButtonBox()
Hides the button box (Ok/Cancel) and enables auto-commit.
QVariant currentValue() const
Returns the current value of the attached editor widget.
Modify current selection to include only select features which match.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QgsFields fields() const
Returns the list of fields of this layer.
void setFrameShape(Shape)
This class contains context information for attribute editor widgets.
QObject * sender() const
Manages an editor widget Widget and wrapper share the same parent.
QString & prepend(QChar ch)
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:124
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.
const QObjectList & children() const
Use a layout with tabs and group boxes. Needs to be configured.
void addAction(QAction *action)
void insert(int i, const T &value)
Q_DECL_DEPRECATED void setIsAddDialog(bool isAddDialog)
Toggles the form mode between edit feature and add feature.
bool isVisible() const
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:42
void setAlignment(QFlags< Qt::AlignmentFlag >)
void setAttributes(const QgsAttributes &attrs)
Sets the feature&#39;s attributes.
Definition: qgsfeature.cpp:115
bool readOnly(int idx) const
This returns true if the field is manually set to read only or if the field does not support editing ...
This element will load a field&#39;s widget onto the form.
This element will load a relation editor onto the form.
Set selection, removing any existing selection.
bool addFeature(QgsFeature &f, bool alsoUpdateExtent=true)
Adds a feature.
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
QString widgetType(int fieldIdx) const
Get the id for the editor widget used to represent the field at the given index.
QgsEditFormConfig * editFormConfig() const
Get the configuration of the form used to represent this vector layer.
void setWorkingDirectory(const QDir &dir)
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.
Mode mode() const
Returns the current mode of the form.
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 setMode(Mode mode)
Sets the current mode of the form.
QSize size() const
void setContext(const QgsAttributeEditorContext &context)
Set the context in which this widget is shown.
const char * name() const
void showButtonBox()
Shows the button box (Ok/Cancel) and disables auto-commit.
void setConfig(const QgsEditorWidgetConfig &config)
Will set the config of this wrapper to the specified config.
void setEnabled(bool)
void addWidget(QWidget *widget, int stretch, QFlags< Qt::AlignmentFlag > alignment)
void append(const T &value)
QVariant property(const char *name) const
void setRowStretch(int row, int stretch)
void setLayout(QLayout *layout)
const_iterator constEnd() const
void installEventFilter(QObject *filterObj)
Do not use python code at all.
int toInt(bool *ok) const
bool isNull() const
QString attributeDisplayName(int attributeIndex) const
Convenience function that returns the attribute alias if defined or the field name else...
const QgsFeatureIds & selectedFeaturesIds() const
Return reference to identifiers of selected features.
void setFeature(const QgsFeature &feature)
Update all editors to correspond to a different feature.
bool popWidget(QgsMessageBarItem *item)
Remove the passed widget from the bar (if previously added), then display the next one in the stack i...
QgsRelation relation(const QString &id) const
Get access to a relation by its id.
bool hasChanged() const
Returns true if the widget&#39;s value has been changed since it was initialized.
QgsAttributes attributes() const
Returns the feature&#39;s attributes.
Definition: qgsfeature.cpp:110
virtual int capabilities() const
Returns a bitmask containing the supported capabilities Note, some capabilities may change depending ...
QgsEditorWidgetWrapper * create(const QString &widgetId, QgsVectorLayer *vl, int fieldIdx, const QgsEditorWidgetConfig &config, QWidget *editor, QWidget *parent, const QgsAttributeEditorContext &context=QgsAttributeEditorContext())
Create an attribute editor widget wrapper of a given type for a given field.
void filterExpressionSet(const QString &expression, QgsAttributeForm::FilterType type)
Is emitted when a filter expression is set using the form.
QString name() const
Gets the name of the field.
Definition: qgsfield.cpp:84
const QgsFields * fields() const
Returns the field map associated with the feature.
Definition: qgsfeature.cpp:188
bool isEmpty() const
void setObjectName(const QString &name)
const T & value() const
void setFocusProxy(QWidget *w)
bool isEmpty() const
void setText(const QString &text)
const_iterator constEnd() const
void remove(int i)
void triggerRepaint()
Will advice the map canvas (and any other interested party) that this layer requires to be repainted...
int addTab(QWidget *page, const QString &label)
This class wraps a request for features to a vector layer (or directly its vector data provider)...
void setOverrideCursor(const QCursor &cursor)
void modeChanged(QgsAttributeForm::Mode mode)
Emitted when the form changes mode.
AttributeEditorType type() const
The type of this element.
void destroyEditCommand()
Destroy active command and reverts all changes in it.
void restoreOverrideCursor()
QgsEditorWidgetConfig widgetConfig(int fieldIdx) const
Get the configuration for the editor widget used to represent the field at the given index...
void refreshFeature()
reload current feature
virtual void setValue(const QVariant &value)=0
Is called, when the value of the widget needs to be changed.
QString currentFilterExpression() const
Creates an expression matching the current search filter value and search properties represented in t...
int count() const
Return number of items.
Definition: qgsfield.cpp:365
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:44
void hide()
int remove(const Key &key)
int count() const
void beforeSave(bool &ok)
Will be emitted before the feature is saved.
void setMargin(int margin)
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
void disconnectButtonBox()
Disconnects the button box (Ok/Cancel) from the accept/resetValues slots If this method is called...
const QgsField & at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:385
void setSizePolicy(QSizePolicy)
void addWidget(QWidget *w)
bool eventFilter(QObject *object, QEvent *event) override
Intercepts keypress on custom form (escape should not close it)
Q_DECL_DEPRECATED void setFields(const QgsFields *fields, bool initAttributes=false)
Assign a field map with the feature to allow attribute access by attribute name.
Definition: qgsfeature.cpp:173
Q_DECL_DEPRECATED bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &value, bool emitSignal)
Changes an attribute value (but does not commit it)
void endEditCommand()
Finish edit command and add it to undo/redo stack.
int indexFromName(const QString &name) const
Look up field&#39;s index from name. Returns -1 on error.
Definition: qgsfield.cpp:424
void clear()
const T value(const Key &key) const
int key() const
QList< QgsAttributeEditorElement * > children() const
Get a list of the children elements of this container.
static bool eval(const QString &command, QString &result)
Eval a python statement.
This class helps to support legacy open form scripts to be compatible with the new QgsAttributeForm s...
void featureSaved(const QgsFeature &feature)
Is emitted, when a feature is changed or added.
void setWidgetResizable(bool resizable)
virtual void close()
const_iterator constBegin() const
bool contains(const T &value) const
virtual bool acceptChanges(const QgsFeature &feature)
void setFrameShadow(Shadow)
const Key key(const T &value) const
void setValid(bool validity)
Sets the validity of the feature.
Definition: qgsfeature.cpp:204
void setMode(Mode mode)
Sets the current mode for the widget.
SelectBehaviour
Selection behaviour.
void addLayout(QLayout *layout, int row, int column, QFlags< Qt::AlignmentFlag > alignment)
Add selection to current selection.
void setWindowFlags(QFlags< Qt::WindowType > type)
const T & at(int i) const
QVariant value(const QString &key, const QVariant &defaultValue) const
const_iterator constBegin() const
QgsAttributeForm(QgsVectorLayer *vl, const QgsFeature &feature=QgsFeature(), const QgsAttributeEditorContext &context=QgsAttributeEditorContext(), QWidget *parent=nullptr)
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.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a python statement.
void addStretch(int stretch)
Q_DECL_DEPRECATED QString editFormInit() const
Get python function for edit form initialization.
void setTitle(const QString &title)
virtual void setIsGroupBox(bool isGroupBox)
Determines if this container is rendered as collapsible group box or tab in a tabwidget.
Load a .ui file for the layout. Needs to be configured.
This class manages a set of relations between layers.
QString initFunction() const
Get python function for edit form initialization.
void addItem(QLayoutItem *item, int row, int column, int rowSpan, int columnSpan, QFlags< Qt::AlignmentFlag > alignment)
int columnCount() const
Get the number of columns in this group.
static QgsProject * instance()
access to canonical QgsProject instance
Definition: qgsproject.cpp:388
QWidget(QWidget *parent, QFlags< Qt::WindowType > f)
virtual QVariant value() const =0
Will be used to access the widget&#39;s value.
void setPopupMode(ToolButtonPopupMode mode)
int count(const T &value) const
QList< QgsAttributeEditorElement * > tabs() const
Returns a list of tabs for EditorLayout::TabLayout.
void createSearchWidgetWrappers(const QString &widgetId, int fieldIdx, const QgsEditorWidgetConfig &config, const QgsAttributeEditorContext &context=QgsAttributeEditorContext())
Creates the search widget wrappers for the widget used when the form is in search mode...
int size() const
Return number of items.
Definition: qgsfield.cpp:370
This is a container for attribute editors, used to group them visually in the attribute form if it is...
QWidget * load(QIODevice *device, QWidget *parentWidget)
void addInterface(QgsAttributeFormInterface *iface)
Takes ownership.
bool isEmpty() const
QString initCode() const
Get python code for edit form initialization.
qint64 QgsFeatureId
Definition: qgsfeature.h:31
void setText(const QString &text)
bool isValid() const
Remove from current selection.
QPushButton * button(StandardButton which) const
#define FID_IS_NEW(fid)
Definition: qgsfeature.h:87
const QgsFeature & feature()
iterator insert(const Key &key, const T &value)
void show()
bool isEmpty() const
QWidget * widget()
Access the widget managed by this wrapper.
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.
Allows modification of attribute values.
void resetSearch()
Resets the search/filter value of the widget.
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.
#define tr(sourceText)