QGIS API Documentation
qgsrelationreferencewidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsrelationreferencewidget.cpp
3  --------------------------------------
4  Date : 20.4.2013
5  Copyright : (C) 2013 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 
17 
18 #include <QPushButton>
19 #include <QDialog>
20 #include <QHBoxLayout>
21 #include <QTimer>
22 
23 #include "qgsattributeform.h"
24 #include "qgsattributedialog.h"
25 #include "qgsapplication.h"
26 #include "qgscollapsiblegroupbox.h"
27 #include "qgseditorwidgetfactory.h"
28 #include "qgsexpression.h"
29 #include "qgsfield.h"
30 #include "qgsgeometry.h"
31 #include "qgsmapcanvas.h"
32 #include "qgsmessagebar.h"
34 #include "qgsvectorlayer.h"
35 #include "qgsattributetablemodel.h"
36 
38  : QWidget( parent )
39  , mEditorContext( QgsAttributeEditorContext() )
40  , mCanvas( nullptr )
41  , mMessageBar( nullptr )
42  , mForeignKey( QVariant() )
43  , mReferencedFieldIdx( -1 )
44  , mReferencingFieldIdx( -1 )
45  , mAllowNull( true )
46  , mHighlight( nullptr )
47  , mMapTool( nullptr )
48  , mMessageBarItem( nullptr )
49  , mRelationName( "" )
50  , mReferencedAttributeForm( nullptr )
51  , mReferencedLayer( nullptr )
52  , mReferencingLayer( nullptr )
53  , mMasterModel( nullptr )
54  , mFilterModel( nullptr )
55  , mFeatureListModel( nullptr )
56  , mWindowWidget( nullptr )
57  , mShown( false )
58  , mIsEditable( true )
59  , mEmbedForm( false )
60  , mReadOnlySelector( false )
61  , mAllowMapIdentification( false )
62  , mOrderByValue( false )
63  , mOpenFormButtonVisible( true )
64  , mChainFilters( false )
65  , mAllowAddFeatures( false )
66 {
67  mTopLayout = new QVBoxLayout( this );
68  mTopLayout->setContentsMargins( 0, 0, 0, 0 );
69 
70  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::Fixed );
71 
72  setLayout( mTopLayout );
73 
74  QHBoxLayout* editLayout = new QHBoxLayout();
75  editLayout->setContentsMargins( 0, 0, 0, 0 );
76  editLayout->setSpacing( 2 );
77 
78  // Prepare the container and layout for the filter comboboxes
79  mChooserContainer = new QWidget;
80  editLayout->addWidget( mChooserContainer );
81  QHBoxLayout* chooserLayout = new QHBoxLayout;
82  chooserLayout->setContentsMargins( 0, 0, 0, 0 );
83  mFilterLayout = new QHBoxLayout;
84  mFilterLayout->setContentsMargins( 0, 0, 0, 0 );
85  mFilterContainer = new QWidget;
86  mFilterContainer->setLayout( mFilterLayout );
87  mChooserContainer->setLayout( chooserLayout );
88  chooserLayout->addWidget( mFilterContainer );
89 
90  // combobox (for non-geometric relation)
91  mComboBox = new QComboBox();
92  mChooserContainer->layout()->addWidget( mComboBox );
93 
94  // read-only line edit
95  mLineEdit = new QLineEdit();
96  mLineEdit->setReadOnly( true );
97  editLayout->addWidget( mLineEdit );
98 
99  // open form button
100  mOpenFormButton = new QToolButton();
101  mOpenFormButton->setIcon( QgsApplication::getThemeIcon( "/mActionPropertyItem.png" ) );
102  mOpenFormButton->setText( tr( "Open related feature form" ) );
103  editLayout->addWidget( mOpenFormButton );
104 
105  mAddEntryButton = new QToolButton();
106  mAddEntryButton->setIcon( QgsApplication::getThemeIcon( "/mActionAdd.svg" ) );
107  mAddEntryButton->setText( tr( "Add new entry" ) );
108  editLayout->addWidget( mAddEntryButton );
109 
110  // highlight button
111  mHighlightFeatureButton = new QToolButton( this );
112  mHighlightFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
113  mHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( "/mActionHighlightFeature.svg" ), tr( "Highlight feature" ), this );
114  mScaleHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( "/mActionScaleHighlightFeature.svg" ), tr( "Scale and highlight feature" ), this );
115  mPanHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( "/mActionPanHighlightFeature.svg" ), tr( "Pan and highlight feature" ), this );
116  mHighlightFeatureButton->addAction( mHighlightFeatureAction );
117  mHighlightFeatureButton->addAction( mScaleHighlightFeatureAction );
118  mHighlightFeatureButton->addAction( mPanHighlightFeatureAction );
119  mHighlightFeatureButton->setDefaultAction( mHighlightFeatureAction );
120  editLayout->addWidget( mHighlightFeatureButton );
121 
122  // map identification button
123  mMapIdentificationButton = new QToolButton( this );
124  mMapIdentificationButton->setIcon( QgsApplication::getThemeIcon( "/mActionMapIdentification.svg" ) );
125  mMapIdentificationButton->setText( tr( "Select on map" ) );
126  mMapIdentificationButton->setCheckable( true );
127  editLayout->addWidget( mMapIdentificationButton );
128 
129  // remove foreign key button
130  mRemoveFKButton = new QToolButton( this );
131  mRemoveFKButton->setIcon( QgsApplication::getThemeIcon( "/mActionRemove.svg" ) );
132  mRemoveFKButton->setText( tr( "No selection" ) );
133  editLayout->addWidget( mRemoveFKButton );
134 
135  // add line to top layout
136  mTopLayout->addLayout( editLayout );
137 
138  // embed form
139  mAttributeEditorFrame = new QgsCollapsibleGroupBox( this );
140  mAttributeEditorLayout = new QVBoxLayout( mAttributeEditorFrame );
141  mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
142  mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
143  mTopLayout->addWidget( mAttributeEditorFrame );
144 
145  // invalid label
146  mInvalidLabel = new QLabel( tr( "The relation is not valid. Please make sure your relation definitions are ok." ) );
147  mInvalidLabel->setWordWrap( true );
148  QFont font = mInvalidLabel->font();
149  font.setItalic( true );
150  mInvalidLabel->setStyleSheet( "QLabel { color: red; } " );
151  mInvalidLabel->setFont( font );
152  mTopLayout->addWidget( mInvalidLabel );
153 
154  // default mode is combobox, no geometric relation and no embed form
155  mLineEdit->hide();
156  mMapIdentificationButton->hide();
157  mHighlightFeatureButton->hide();
158  mAttributeEditorFrame->hide();
159  mInvalidLabel->hide();
160 
161  // connect buttons
162  connect( mOpenFormButton, SIGNAL( clicked() ), this, SLOT( openForm() ) );
163  connect( mHighlightFeatureButton, SIGNAL( triggered( QAction* ) ), this, SLOT( highlightActionTriggered( QAction* ) ) );
164  connect( mMapIdentificationButton, SIGNAL( clicked() ), this, SLOT( mapIdentification() ) );
165  connect( mRemoveFKButton, SIGNAL( clicked() ), this, SLOT( deleteForeignKey() ) );
166  connect( mAddEntryButton, SIGNAL( clicked( bool ) ), this, SLOT( addEntry() ) );
167  connect( mComboBox, SIGNAL( editTextChanged( QString ) ), this, SLOT( updateAddEntryButton() ) );
168 }
169 
171 {
172  deleteHighlight();
173  unsetMapTool();
174  if ( mMapTool )
175  delete mMapTool;
176 }
177 
178 void QgsRelationReferenceWidget::setRelation( const QgsRelation& relation, bool allowNullValue )
179 {
180  mAllowNull = allowNullValue;
181  mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
182 
183  if ( relation.isValid() )
184  {
185  mInvalidLabel->hide();
186 
187  mRelation = relation;
188  mReferencingLayer = relation.referencingLayer();
189  mRelationName = relation.name();
190  mReferencedLayer = relation.referencedLayer();
191  mReferencedFieldIdx = mReferencedLayer->fieldNameIndex( relation.fieldPairs().at( 0 ).second );
192  mReferencingFieldIdx = mReferencingLayer->fieldNameIndex( relation.fieldPairs().at( 0 ).first );
193 
195 
196  if ( mEmbedForm )
197  {
198  mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
199  mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
200  mReferencedAttributeForm->hideButtonBox();
201  mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
202  }
203 
204  connect( mReferencedLayer, SIGNAL( editingStarted() ), this, SLOT( updateAddEntryButton() ) );
205  connect( mReferencedLayer, SIGNAL( editingStopped() ), this, SLOT( updateAddEntryButton() ) );
206  updateAddEntryButton();
207  }
208  else
209  {
210  mInvalidLabel->show();
211  }
212 
213  if ( mShown && isVisible() )
214  {
215  init();
216  }
217 }
218 
220 {
221  if ( !editable )
222  unsetMapTool();
223 
224  mFilterContainer->setEnabled( editable );
225  mComboBox->setEnabled( editable );
226  mComboBox->setEditable( true );
227  mMapIdentificationButton->setEnabled( editable );
228  mRemoveFKButton->setEnabled( editable );
229  mIsEditable = editable;
230 }
231 
233 {
234  if ( !value.isValid() )
235  {
236  return;
237  }
238  if ( value.isNull() )
239  {
241  return;
242  }
243 
244  if ( !mReferencedLayer )
245  return;
246 
247  // Attributes from the referencing layer
248  QgsAttributes attrs = QgsAttributes( mReferencingLayer->fields().count() );
249  // Set the value on the foreign key field of the referencing record
250  attrs[ mReferencingLayer->fieldNameIndex( mRelation.fieldPairs().at( 0 ).first )] = value;
251 
252  QgsFeatureRequest request = mRelation.getReferencedFeatureRequest( attrs );
253 
254  mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
255 
256  if ( !mFeature.isValid() )
257  {
258  return;
259  }
260 
261  mForeignKey = mFeature.attribute( mReferencedFieldIdx );
262 
263  if ( mReadOnlySelector )
264  {
265  QgsExpression expr( mReferencedLayer->displayExpression() );
266  QgsExpressionContext context;
269  << QgsExpressionContextUtils::layerScope( mReferencedLayer );
270  context.setFeature( mFeature );
271  QString title = expr.evaluate( &context ).toString();
272  if ( expr.hasEvalError() )
273  {
274  title = mFeature.attribute( mReferencedFieldIdx ).toString();
275  }
276  mLineEdit->setText( title );
277  }
278  else
279  {
280  int i = mComboBox->findData( mFeature.id(), QgsAttributeTableModel::FeatureIdRole );
281  if ( i == -1 && mAllowNull )
282  {
283  mComboBox->setCurrentIndex( 0 );
284  }
285  else
286  {
287  mComboBox->setCurrentIndex( i );
288  }
289  }
290 
291  mRemoveFKButton->setEnabled( mIsEditable );
292  highlightFeature( mFeature );
293  updateAttributeEditorFrame( mFeature );
294  emit foreignKeyChanged( foreignKey() );
295 }
296 
298 {
299  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
300  if ( mReadOnlySelector )
301  {
302  QString nullText = "";
303  if ( mAllowNull )
304  {
305  nullText = tr( "%1 (no selection)" ).arg( nullValue.toString() );
306  }
307  mLineEdit->setText( nullText );
308  mForeignKey = QVariant();
309  mFeature.setValid( false );
310  }
311  else
312  {
313  if ( mAllowNull )
314  {
315  mComboBox->setCurrentIndex( 0 );
316  }
317  else
318  {
319  mComboBox->setCurrentIndex( -1 );
320  }
321  }
322  mRemoveFKButton->setEnabled( false );
323  updateAttributeEditorFrame( QgsFeature() );
324  emit foreignKeyChanged( QVariant( QVariant::Int ) );
325 }
326 
328 {
329  QgsFeature f;
330  if ( mReferencedLayer )
331  {
332  QgsFeatureId fid;
333  if ( mReadOnlySelector )
334  {
335  fid = mFeature.id();
336  }
337  else
338  {
339  fid = mComboBox->itemData( mComboBox->currentIndex(), QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
340  }
341  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( f );
342  }
343  return f;
344 }
345 
347 {
348  if ( mReadOnlySelector )
349  {
350  whileBlocking( mLineEdit )->setText( QString() );
351  }
352  else
353  {
354  whileBlocking( mComboBox )->setCurrentIndex( -1 );
355  }
356  mRemoveFKButton->setEnabled( false );
357  updateAttributeEditorFrame( QgsFeature() );
358 }
359 
361 {
362  if ( mReadOnlySelector )
363  {
364  return mForeignKey;
365  }
366  else
367  {
368  if ( mReferencingFieldIdx < 0 || mReferencingFieldIdx >= mReferencingLayer->fields().count() )
369  {
370  return QVariant();
371  }
372  else if ( !mFeature.isValid() )
373  {
374  return QVariant( mReferencingLayer->fields().at( mReferencingFieldIdx ).type() );
375  }
376  else
377  {
378  return mFeature.attribute( mReferencedFieldIdx );
379  }
380  }
381 }
382 
384 {
385  mEditorContext = context;
386  mCanvas = canvas;
387  mMessageBar = messageBar;
388 
389  if ( mMapTool )
390  delete mMapTool;
391  mMapTool = new QgsMapToolIdentifyFeature( mCanvas );
392  mMapTool->setButton( mMapIdentificationButton );
393 }
394 
396 {
397  if ( display )
398  {
399  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
400  mTopLayout->setAlignment( Qt::AlignTop );
401  }
402 
403  mAttributeEditorFrame->setVisible( display );
404  mEmbedForm = display;
405 }
406 
408 {
409  mChooserContainer->setHidden( readOnly );
410  mLineEdit->setVisible( readOnly );
411  mRemoveFKButton->setVisible( mAllowNull && readOnly );
412  mReadOnlySelector = readOnly;
413 }
414 
416 {
417  mHighlightFeatureButton->setVisible( allowMapIdentification );
418  mMapIdentificationButton->setVisible( allowMapIdentification );
419  mAllowMapIdentification = allowMapIdentification;
420 }
421 
423 {
424  mOrderByValue = orderByValue;
425 }
426 
428 {
429  mFilterFields = filterFields;
430 }
431 
433 {
434  mOpenFormButton->setVisible( openFormButtonVisible );
435  mOpenFormButtonVisible = openFormButtonVisible;
436 }
437 
439 {
440  mChainFilters = chainFilters;
441 }
442 
444 {
445  Q_UNUSED( e )
446 
447  mShown = true;
448 
449  init();
450 }
451 
453 {
454  if ( !mReadOnlySelector && mComboBox->count() == 0 && mReferencedLayer )
455  {
456  QApplication::setOverrideCursor( Qt::WaitCursor );
457 
458  QSet<QString> requestedAttrs;
459 
460  QgsVectorLayerCache* layerCache = new QgsVectorLayerCache( mReferencedLayer, 100000, this );
461 
462  if ( !mFilterFields.isEmpty() )
463  {
464  Q_FOREACH ( const QString& fieldName, mFilterFields )
465  {
466  QVariantList uniqueValues;
467  int idx = mReferencedLayer->fieldNameIndex( fieldName );
468  QComboBox* cb = new QComboBox();
469  cb->setProperty( "Field", fieldName );
470  mFilterComboBoxes << cb;
471  mReferencedLayer->uniqueValues( idx, uniqueValues );
472  cb->addItem( mReferencedLayer->attributeAlias( idx ).isEmpty() ? fieldName : mReferencedLayer->attributeAlias( idx ) );
473  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
474  cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->fields().at( idx ).type() ) );
475 
476  Q_FOREACH ( const QVariant& v, uniqueValues )
477  {
478  cb->addItem( v.toString(), v );
479  }
480 
481  connect( cb, SIGNAL( currentIndexChanged( int ) ), this, SLOT( filterChanged() ) );
482 
483  // Request this attribute for caching
484  requestedAttrs << fieldName;
485 
486  mFilterLayout->addWidget( cb );
487  }
488 
489  if ( mChainFilters )
490  {
491  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
492 
493  QgsFeature ft;
494  QgsFeatureIterator fit = layerCache->getFeatures();
495  while ( fit.nextFeature( ft ) )
496  {
497  for ( int i = 0; i < mFilterComboBoxes.count() - 1; ++i )
498  {
499  QVariant cv = ft.attribute( mFilterFields[i] );
500  QVariant nv = ft.attribute( mFilterFields[i + 1] );
501  QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
502  QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
503  mFilterCache[mFilterFields[i]][cf] << nf;
504  }
505  }
506  }
507  }
508  else
509  {
510  mFilterContainer->hide();
511  }
512 
513  QgsExpression exp( mReferencedLayer->displayExpression() );
514 
515  requestedAttrs += exp.referencedColumns().toSet();
516  requestedAttrs << mRelation.fieldPairs().at( 0 ).second;
517 
518  QgsAttributeList attributes;
519  Q_FOREACH ( const QString& attr, requestedAttrs )
520  attributes << mReferencedLayer->fieldNameIndex( attr );
521 
522  layerCache->setCacheSubsetOfAttributes( attributes );
523  mMasterModel = new QgsAttributeTableModel( layerCache );
524  mMasterModel->setRequest( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( requestedAttrs.toList(), mReferencedLayer->fields() ) );
525  mFilterModel = new QgsAttributeTableFilterModel( mCanvas, mMasterModel, mMasterModel );
526  mFeatureListModel = new QgsFeatureListModel( mFilterModel, this );
527  mFeatureListModel->setDisplayExpression( mReferencedLayer->displayExpression() );
528 
529  mMasterModel->loadLayer();
530 
531  mFeatureListModel->setInjectNull( mAllowNull );
532  if ( mOrderByValue )
533  {
534  const QStringList referencedColumns = QgsExpression( mReferencedLayer->displayExpression() ).referencedColumns();
535  if ( !referencedColumns.isEmpty() )
536  {
537  int sortIdx = mReferencedLayer->fieldNameIndex( referencedColumns.first() );
538  mFilterModel->sort( sortIdx );
539  }
540  }
541 
542  mComboBox->setModel( mFeatureListModel );
543 
544  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
545 
546  if ( mChainFilters && mFeature.isValid() )
547  {
548  for ( int i = 0; i < mFilterFields.size(); i++ )
549  {
550  QVariant v = mFeature.attribute( mFilterFields[i] );
551  QString f = v.isNull() ? nullValue.toString() : v.toString();
552  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
553  }
554  }
555 
556  QVariant featId = mFeature.isValid() ? mFeature.id() : QVariant( QVariant::Int );
557  mComboBox->setCurrentIndex( mComboBox->findData( featId, QgsAttributeTableModel::FeatureIdRole ) );
558 
559  // Only connect after iterating, to have only one iterator on the referenced table at once
560  connect( mComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( comboReferenceChanged( int ) ) );
562  }
563 }
564 
565 void QgsRelationReferenceWidget::highlightActionTriggered( QAction* action )
566 {
567  if ( action == mHighlightFeatureAction )
568  {
569  highlightFeature();
570  }
571  else if ( action == mScaleHighlightFeatureAction )
572  {
573  highlightFeature( QgsFeature(), Scale );
574  }
575  else if ( action == mPanHighlightFeatureAction )
576  {
577  highlightFeature( QgsFeature(), Pan );
578  }
579 }
580 
582 {
583  QgsFeature feat = referencedFeature();
584 
585  if ( !feat.isValid() )
586  return;
587 
589  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
590  attributeDialog.exec();
591 }
592 
593 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
594 {
595  if ( !mCanvas )
596  return;
597 
598  if ( !f.isValid() )
599  {
600  f = referencedFeature();
601  if ( !f.isValid() )
602  return;
603  }
604 
605  if ( !f.constGeometry() )
606  {
607  return;
608  }
609 
610  const QgsGeometry* geom = f.constGeometry();
611 
612  // scale or pan
613  if ( canvasExtent == Scale )
614  {
615  QgsRectangle featBBox = geom->boundingBox();
616  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
617  QgsRectangle extent = mCanvas->extent();
618  if ( !extent.contains( featBBox ) )
619  {
620  extent.combineExtentWith( &featBBox );
621  extent.scale( 1.1 );
622  mCanvas->setExtent( extent );
623  mCanvas->refresh();
624  }
625  }
626  else if ( canvasExtent == Pan )
627  {
628  QgsGeometry* centroid = geom->centroid();
629  QgsPoint center = centroid->asPoint();
630  delete centroid;
631  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
632  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
633  }
634 
635  // highlight
636  deleteHighlight();
637  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
638  QSettings settings;
639  QColor color = QColor( settings.value( "/Map/highlight/color", QGis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
640  int alpha = settings.value( "/Map/highlight/colorAlpha", QGis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
641  double buffer = settings.value( "/Map/highlight/buffer", QGis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
642  double minWidth = settings.value( "/Map/highlight/minWidth", QGis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
643 
644  mHighlight->setColor( color ); // sets also fill with default alpha
645  color.setAlpha( alpha );
646  mHighlight->setFillColor( color ); // sets fill with alpha
647  mHighlight->setBuffer( buffer );
648  mHighlight->setMinWidth( minWidth );
649  mHighlight->show();
650 
651  QTimer* timer = new QTimer( this );
652  timer->setSingleShot( true );
653  connect( timer, SIGNAL( timeout() ), this, SLOT( deleteHighlight() ) );
654  timer->start( 3000 );
655 }
656 
657 void QgsRelationReferenceWidget::deleteHighlight()
658 {
659  if ( mHighlight )
660  {
661  mHighlight->hide();
662  delete mHighlight;
663  }
664  mHighlight = nullptr;
665 }
666 
668 {
669  if ( !mAllowMapIdentification || !mReferencedLayer )
670  return;
671 
672  const QgsVectorLayerTools* tools = mEditorContext.vectorLayerTools();
673  if ( !tools )
674  return;
675  if ( !mCanvas )
676  return;
677 
678  mMapTool->setLayer( mReferencedLayer );
679  mCanvas->setMapTool( mMapTool );
680 
681  mWindowWidget = window();
682 
683  mCanvas->window()->raise();
684  mCanvas->activateWindow();
685  mCanvas->setFocus();
686 
687  connect( mMapTool, SIGNAL( featureIdentified( QgsFeature ) ), this, SLOT( featureIdentified( const QgsFeature ) ) );
688  connect( mMapTool, SIGNAL( deactivated() ), this, SLOT( mapToolDeactivated() ) );
689 
690  if ( mMessageBar )
691  {
692  QString title = tr( "Relation %1 for %2." ).arg( mRelationName, mReferencingLayer->name() );
693  QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
694  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
695  mMessageBar->pushItem( mMessageBarItem );
696  }
697 }
698 
699 void QgsRelationReferenceWidget::comboReferenceChanged( int index )
700 {
702  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( mFeature );
703  highlightFeature( mFeature );
704  updateAttributeEditorFrame( mFeature );
705  emit foreignKeyChanged( mFeature.attribute( mReferencedFieldIdx ) );
706 }
707 
708 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature& feature )
709 {
710  // Check if we're running with an embedded frame we need to update
711  if ( mAttributeEditorFrame )
712  {
713  if ( mReferencedAttributeForm )
714  {
715  mReferencedAttributeForm->setFeature( feature );
716  }
717  }
718 }
719 
721 {
722  return mAllowAddFeatures;
723 }
724 
726 {
727  mAllowAddFeatures = allowAddFeatures;
728  updateAddEntryButton();
729 }
730 
731 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature& feature )
732 {
733  if ( mReadOnlySelector )
734  {
735  QgsExpression expr( mReferencedLayer->displayExpression() );
736  QgsExpressionContext context;
739  << QgsExpressionContextUtils::layerScope( mReferencedLayer );
740  context.setFeature( feature );
741  QString title = expr.evaluate( &context ).toString();
742  if ( expr.hasEvalError() )
743  {
744  title = feature.attribute( mReferencedFieldIdx ).toString();
745  }
746  mLineEdit->setText( title );
747  mForeignKey = feature.attribute( mReferencedFieldIdx );
748  mFeature = feature;
749  }
750  else
751  {
752  mComboBox->setCurrentIndex( mComboBox->findData( feature.attribute( mReferencedFieldIdx ), QgsAttributeTableModel::FeatureIdRole ) );
753  mFeature = feature;
754  }
755 
756  mRemoveFKButton->setEnabled( mIsEditable );
757  highlightFeature( feature );
758  updateAttributeEditorFrame( feature );
759  emit foreignKeyChanged( foreignKey() );
760 
761  unsetMapTool();
762 }
763 
764 void QgsRelationReferenceWidget::unsetMapTool()
765 {
766  // deactivate map tool if activated
767  if ( mCanvas && mMapTool )
768  {
769  /* this will call mapToolDeactivated */
770  mCanvas->unsetMapTool( mMapTool );
771  }
772 }
773 
774 void QgsRelationReferenceWidget::mapToolDeactivated()
775 {
776  if ( mWindowWidget )
777  {
778  mWindowWidget->raise();
779  mWindowWidget->activateWindow();
780  }
781 
782  if ( mMessageBar && mMessageBarItem )
783  {
784  mMessageBar->popWidget( mMessageBarItem );
785  }
786  mMessageBarItem = nullptr;
787 }
788 
789 void QgsRelationReferenceWidget::filterChanged()
790 {
791  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
792 
793  QStringList filters;
794  QgsAttributeList attrs;
795 
796  QComboBox* scb = qobject_cast<QComboBox*>( sender() );
797 
798  Q_ASSERT( scb );
799 
800  if ( mChainFilters )
801  {
802  QComboBox* ccb = nullptr;
803  Q_FOREACH ( QComboBox* cb, mFilterComboBoxes )
804  {
805  if ( !ccb )
806  {
807  if ( cb == scb )
808  ccb = cb;
809 
810  continue;
811  }
812 
813  if ( ccb->currentIndex() == 0 )
814  {
815  cb->setCurrentIndex( 0 );
816  cb->setEnabled( false );
817  }
818  else
819  {
820  cb->blockSignals( true );
821  cb->clear();
822  cb->addItem( cb->property( "Field" ).toString() );
823 
824  // ccb = scb
825  // cb = scb + 1
826  Q_FOREACH ( const QString& txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
827  {
828  cb->addItem( txt );
829  }
830 
831  cb->setEnabled( true );
832  cb->blockSignals( false );
833 
834  ccb = cb;
835  }
836  }
837  }
838 
839  Q_FOREACH ( QComboBox* cb, mFilterComboBoxes )
840  {
841  if ( cb->currentIndex() != 0 )
842  {
843  const QString fieldName = cb->property( "Field" ).toString();
844 
845  if ( cb->currentText() == nullValue.toString() )
846  {
847  filters << QString( "\"%1\" IS NULL" ).arg( fieldName );
848  }
849  else
850  {
851  if ( mReferencedLayer->fields().field( fieldName ).type() == QVariant::String )
852  {
853  filters << QString( "\"%1\" = '%2'" ).arg( fieldName, cb->currentText() );
854  }
855  else
856  {
857  filters << QString( "\"%1\" = %2" ).arg( fieldName, cb->currentText() );
858  }
859  }
860  attrs << mReferencedLayer->fieldNameIndex( fieldName );
861  }
862  }
863 
864  QString filterExpression = filters.join( " AND " );
865 
866  QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( filterExpression ).setSubsetOfAttributes( attrs ) ) );
867 
868  QgsFeature f;
869  QgsFeatureIds featureIds;
870 
871  while ( it.nextFeature( f ) )
872  {
873  featureIds << f.id();
874  }
875 
876  mFilterModel->setFilteredFeatures( featureIds );
877 }
878 
879 void QgsRelationReferenceWidget::addEntry()
880 {
881  QgsFeature f( mReferencedLayer->fields() );
882  QgsAttributeMap attributes;
883 
884  // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
885  if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
886  {
887  int fieldIdx = mReferencedLayer->fieldNameIndex( mReferencedLayer->displayExpression() );
888 
889  if ( fieldIdx != -1 )
890  {
891  attributes.insert( fieldIdx, mComboBox->currentText() );
892  }
893  }
894 
895  if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, QgsGeometry(), &f ) )
896  {
897  int i = mComboBox->findData( f.id(), QgsAttributeTableModel::FeatureIdRole );
898  mComboBox->setCurrentIndex( i );
899  mAddEntryButton->setEnabled( false );
900  }
901 }
902 
903 void QgsRelationReferenceWidget::updateAddEntryButton()
904 {
905  mAddEntryButton->setVisible( mAllowAddFeatures );
906  mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
907 }
QLayout * layout() const
void unsetMapTool(QgsMapTool *mapTool)
Unset the current map tool or last non zoom tool.
QgsFeatureId id() const
Get the feature ID for this feature.
Definition: qgsfeature.cpp:65
Methods in this class are used to handle basic operations on vector layers.
void setEditorContext(const QgsAttributeEditorContext &context, QgsMapCanvas *canvas, QgsMessageBar *messageBar)
When showing a single feature (e.g. district information when looking at the form of a house) ...
void setRequest(const QgsFeatureRequest &request)
Set a request that will be used to fill this attribute table model.
Class for parsing and evaluation of expressions (formerly called "search strings").
Wrapper for iterator of features from vector data provider or vector layer.
bool isValid() const
Returns the validity of this relation.
void setStyleSheet(const QString &styleSheet)
static unsigned index
static double DEFAULT_HIGHLIGHT_BUFFER_MM
Default highlight buffer in mm.
Definition: qgis.h:243
A rectangle specified with double values.
Definition: qgsrectangle.h:35
static const QColor DEFAULT_HIGHLIGHT_COLOR
Default highlight color.
Definition: qgis.h:239
QgsPoint layerToMapCoordinates(QgsMapLayer *theLayer, QgsPoint point) const
transform point coordinates from layer&#39;s CRS to output CRS
const QgsField & field(int fieldIdx) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:390
void setContentsMargins(int left, int top, int right, int bottom)
virtual void loadLayer()
Loads the layer into the model Preferably to be called, before using this model as source for any oth...
void setLayer(QgsVectorLayer *vl)
change the layer used by the map tool to identify
QString name() const
Get the display name of the layer.
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:199
bool setDisplayExpression(const QString &expression)
QWidget * window() const
QString name() const
void hideButtonBox()
Hides the button box (Ok/Cancel) and enables auto-commit.
void addAction(QAction *action)
void setText(const QString &)
A groupbox that collapses/expands when toggled and can save its collapsed and checked states...
void setFilterFields(const QStringList &filterFields)
Set the fields for which filter comboboxes will be created.
QgsFields fields() const
Returns the list of fields of this layer.
void deleteForeignKey()
unset the currently related feature
void foreignKeyChanged(const QVariant &)
void setDefaultAction(QAction *action)
This class contains context information for attribute editor widgets.
void uniqueValues(int index, QList< QVariant > &uniqueValues, int limit=-1)
Returns unique values for column.
QObject * sender() const
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
static QIcon getThemeIcon(const QString &theName)
Helper to get a theme icon.
void setOpenFormButtonVisible(bool openFormButtonVisible)
void setExtent(const QgsRectangle &r)
Set the extent of the map canvas.
bool contains(const QgsRectangle &rect) const
return true when rectangle contains other rectangle
const T & at(int i) const
void scale(double scaleFactor, const QgsPoint *c=nullptr)
Scale the rectangle around its center point.
void clear()
void setFillColor(const QColor &fillColor)
Set polygons fill color.
QgsRectangle boundingBox() const
Returns the bounding box of this feature.
virtual void setVisible(bool visible)
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:42
T value() const
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
QgsVectorLayer * referencedLayer() const
Access the referenced (parent) layer.
int exec()
void refresh()
Repaints the canvas map.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:76
void setAlpha(int alpha)
QVariant foreignKey()
returns the related feature foreign key
QString itemText(int index) const
QString join(const QString &separator) const
QString name() const
Returns a human readable name for this relation.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:187
A model backed by a QgsVectorLayerCache which is able to provide feature/attribute information to a Q...
bool orderByValue()
If the widget will order the combobox entries by value.
void setEditable(bool editable)
const QString displayExpression()
Get the preview expression, used to create a human readable preview string.
QgsFeatureRequest getReferencedFeatureRequest(const QgsAttributes &attributes) const
Creates a request to return the feature on the referenced (parent) layer which is referenced by the p...
virtual bool isEditable() const override
Returns true if the provider is in editing mode.
void setIcon(const QIcon &icon)
QString tr(const char *sourceText, const char *disambiguation, int n)
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:105
void setCacheSubsetOfAttributes(const QgsAttributeList &attributes)
Set the subset of attributes to be cached.
void setAllowMapIdentification(bool allowMapIdentification)
int size() const
bool allowMapIdentification()
determines if the widge offers the possibility to select the related feature on the map (using a dedi...
void setButton(QAbstractButton *button)
Use this to associate a button to this maptool.
Definition: qgsmaptool.cpp:129
void setForeignKey(const QVariant &value)
this sets the related feature using from the foreign key
virtual void setFilteredFeatures(const QgsFeatureIds &ids)
Specify a list of features, which the filter will accept.
void addItem(const QString &text, const QVariant &userData)
void setReadOnly(bool)
void setMapTool(QgsMapTool *mapTool)
Sets the map tool currently being used on the canvas.
void setBuffer(double buffer)
Set line / outline buffer in millimeters.
Definition: qgshighlight.h:63
bool allowAddFeatures() const
Determines if a button for adding new features should be shown.
void setOrderByValue(bool orderByValue)
Set if the widget will order the combobox entries by value.
bool chainFilters() const
Determines if the filters are chained.
static QgsMessageBarItem * createMessage(const QString &text, QWidget *parent=nullptr)
make out a widget containing a message to be displayed on the bar
void setEnabled(bool)
void addWidget(QWidget *widget, int stretch, QFlags< Qt::AlignmentFlag > alignment)
int count(const T &value) const
void combineExtentWith(QgsRectangle *rect)
expand the rectangle so that covers both the original rectangle and the given rectangle ...
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
QVariant property(const char *name) const
void setLayout(QLayout *layout)
int toInt(bool *ok) const
bool isNull() 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...
void setFocus()
virtual void showEvent(QShowEvent *e) override
bool isEmpty() const
QgsVectorLayer * referencingLayer() const
Access the referencing (child) layer This is the layer which has the field(s) which point to another ...
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void raise()
bool isEmpty() const
A class for highlight features on the map.
Definition: qgshighlight.h:36
This class wraps a request for features to a vector layer (or directly its vector data provider)...
void setOverrideCursor(const QCursor &cursor)
void restoreOverrideCursor()
int count() const
Return number of items.
Definition: qgsfield.cpp:365
T & first()
void setCheckable(bool)
void hide()
QgsGeometry * centroid() const
Returns the center of mass of a geometry.
QString attributeAlias(int attributeIndex) const
Returns the alias of an attribute name or an empty string if there is no alias.
QgsVectorLayerCache * layerCache() const
Returns the layer cache this model uses as backend.
void setRelation(const QgsRelation &relation, bool allowNullValue)
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)
A class to represent a point.
Definition: qgspoint.h:117
QVariant itemData(int index, int role) const
bool blockSignals(bool block)
const QFont & font() const
This class caches features of a given QgsVectorLayer.
void setItalic(bool enable)
The QgsMapToolIdentifyFeature class is a map tool to identify a feature on a chosen layer...
void setValid(bool validity)
Sets the validity of the feature.
Definition: qgsfeature.cpp:204
bool setAlignment(QWidget *w, QFlags< Qt::AlignmentFlag > alignment)
QVariant value(const QString &key, const QVariant &defaultValue) const
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:328
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
int findData(const QVariant &data, int role, QFlags< Qt::MatchFlag > flags) const
void activateWindow()
void setColor(const QColor &color)
Set line/outline to color, polygon fill to color with alpha = 63.
void setModel(QAbstractItemModel *model)
virtual void sort(int column, Qt::SortOrder order=Qt::AscendingOrder) override
Sort by the given column using the given order.
void setTitle(const QString &title)
void setAllowAddFeatures(bool allowAddFeatures)
Determines if a button for adding new features should be shown.
QWidget(QWidget *parent, QFlags< Qt::WindowType > f)
void setPopupMode(ToolButtonPopupMode mode)
void showIndeterminateState()
Sets the widget to display in an indeterminate "mixed value" state.
QgsFeature referencedFeature()
return the related feature (from the referenced layer) if no feature is related, it returns an invali...
const QgsVectorLayerTools * vectorLayerTools() const
void setCurrentIndex(int index)
const QgsGeometry * constGeometry() const
Gets a const pointer to the geometry object associated with this feature.
Definition: qgsfeature.cpp:82
virtual bool addFeature(QgsVectorLayer *layer, const QgsAttributeMap &defaultValues=QgsAttributeMap(), const QgsGeometry &defaultGeometry=QgsGeometry(), QgsFeature *feature=nullptr) const =0
This method should/will be called, whenever a new feature will be added to the layer.
QList< FieldPair > fieldPairs() const
Returns the field pairs which form this relation The first element of each pair are the field names f...
qint64 QgsFeatureId
Definition: qgsfeature.h:31
void setText(const QString &text)
void start(int msec)
bool isValid() const
double toDouble(bool *ok) const
bool setProperty(const char *name, const QVariant &value)
QgsRectangle extent() const
Returns the current zoom exent of the map canvas.
void show()
void setInjectNull(bool injectNull)
If true is specified, a NULL value will be injected.
static QgsExpressionContextScope * projectScope()
Creates a new scope which contains variables and functions relating to the current QGIS project...
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
bool nextFeature(QgsFeature &f)
Geometry is not required. It may still be returned if e.g. required for a filter condition.
QgsPoint asPoint() const
Return contents of the geometry as a point if wkbType is WKBPoint, otherwise returns [0...
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)
int fieldNameIndex(const QString &fieldName) const
Returns the index of a field name or -1 if the field does not exist.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &featureRequest=QgsFeatureRequest())
Query this VectorLayerCache for features.
void mapIdentification()
activate the map tool to select a new related feature on the map
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() const
static double DEFAULT_HIGHLIGHT_MIN_WIDTH_MM
Default highlight line/outline minimum width in mm.
Definition: qgis.h:247
void setHidden(bool hidden)
void setWordWrap(bool on)
bool openFormButtonVisible()
determines the open form button is visible in the widget
void setSpacing(int spacing)
void openForm()
open the form of the related feature in a new dialog
void setChainFilters(bool chainFilters)
Set if filters are chained.
void zoomByFactor(double scaleFactor, const QgsPoint *center=nullptr)
Zoom with the factor supplied.
A form was embedded as a widget on another form.
void addLayout(QLayout *layout, int stretch)
void setMinWidth(double width)
Set minimum line / outline width in millimeters.
Definition: qgshighlight.h:67
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:89
void setSingleShot(bool singleShot)