QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 #include <QCompleter>
23 
24 #include "qgsattributeform.h"
26 #include "qgsattributedialog.h"
27 #include "qgsapplication.h"
28 #include "qgscollapsiblegroupbox.h"
29 #include "qgseditorwidgetfactory.h"
30 #include "qgsexpression.h"
31 #include "qgsfeaturelistmodel.h"
32 #include "qgsfields.h"
33 #include "qgsgeometry.h"
34 #include "qgshighlight.h"
35 #include "qgsmapcanvas.h"
36 #include "qgsmessagebar.h"
38 #include "qgsvectorlayer.h"
39 #include "qgsattributetablemodel.h"
41 #include "qgsfeatureiterator.h"
42 #include "qgsfeaturelistcombobox.h"
44 #include "qgsfeaturefiltermodel.h"
45 #include "qgsidentifymenu.h"
46 
47 
49  : QWidget( parent )
50 {
51  mTopLayout = new QVBoxLayout( this );
52  mTopLayout->setContentsMargins( 0, 0, 0, 0 );
53 
54  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::Fixed );
55 
56  setLayout( mTopLayout );
57 
58  QHBoxLayout *editLayout = new QHBoxLayout();
59  editLayout->setContentsMargins( 0, 0, 0, 0 );
60  editLayout->setSpacing( 2 );
61 
62  // Prepare the container and layout for the filter comboboxes
63  mChooserContainer = new QWidget;
64  editLayout->addWidget( mChooserContainer );
65  QHBoxLayout *chooserLayout = new QHBoxLayout;
66  chooserLayout->setContentsMargins( 0, 0, 0, 0 );
67  mFilterLayout = new QHBoxLayout;
68  mFilterLayout->setContentsMargins( 0, 0, 0, 0 );
69  mFilterContainer = new QWidget;
70  mFilterContainer->setLayout( mFilterLayout );
71  mChooserContainer->setLayout( chooserLayout );
72  chooserLayout->addWidget( mFilterContainer );
73 
74  mComboBox = new QgsFeatureListComboBox();
75  mChooserContainer->layout()->addWidget( mComboBox );
76 
77  // read-only line edit
78  mLineEdit = new QLineEdit();
79  mLineEdit->setReadOnly( true );
80  editLayout->addWidget( mLineEdit );
81 
82  // open form button
83  mOpenFormButton = new QToolButton();
84  mOpenFormButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPropertyItem.svg" ) ) );
85  mOpenFormButton->setText( tr( "Open Related Feature Form" ) );
86  editLayout->addWidget( mOpenFormButton );
87 
88  mAddEntryButton = new QToolButton();
89  mAddEntryButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAdd.svg" ) ) );
90  mAddEntryButton->setText( tr( "Add New Entry" ) );
91  editLayout->addWidget( mAddEntryButton );
92 
93  // highlight button
94  mHighlightFeatureButton = new QToolButton( this );
95  mHighlightFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
96  mHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHighlightFeature.svg" ) ), tr( "Highlight feature" ), this );
97  mScaleHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleHighlightFeature.svg" ) ), tr( "Scale and highlight feature" ), this );
98  mPanHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPanHighlightFeature.svg" ) ), tr( "Pan and highlight feature" ), this );
99  mHighlightFeatureButton->addAction( mHighlightFeatureAction );
100  mHighlightFeatureButton->addAction( mScaleHighlightFeatureAction );
101  mHighlightFeatureButton->addAction( mPanHighlightFeatureAction );
102  mHighlightFeatureButton->setDefaultAction( mHighlightFeatureAction );
103  editLayout->addWidget( mHighlightFeatureButton );
104 
105  // map identification button
106  mMapIdentificationButton = new QToolButton( this );
107  mMapIdentificationButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMapIdentification.svg" ) ) );
108  mMapIdentificationButton->setText( tr( "Select on Map" ) );
109  mMapIdentificationButton->setCheckable( true );
110  editLayout->addWidget( mMapIdentificationButton );
111 
112  // remove foreign key button
113  mRemoveFKButton = new QToolButton( this );
114  mRemoveFKButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemove.svg" ) ) );
115  mRemoveFKButton->setText( tr( "No Selection" ) );
116  editLayout->addWidget( mRemoveFKButton );
117 
118  // add line to top layout
119  mTopLayout->addLayout( editLayout );
120 
121  // embed form
122  mAttributeEditorFrame = new QgsCollapsibleGroupBox( this );
123  mAttributeEditorLayout = new QVBoxLayout( mAttributeEditorFrame );
124  mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
125  mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
126  mTopLayout->addWidget( mAttributeEditorFrame );
127 
128  // invalid label
129  mInvalidLabel = new QLabel( tr( "The relation is not valid. Please make sure your relation definitions are OK." ) );
130  mInvalidLabel->setWordWrap( true );
131  QFont font = mInvalidLabel->font();
132  font.setItalic( true );
133  mInvalidLabel->setStyleSheet( QStringLiteral( "QLabel { color: red; } " ) );
134  mInvalidLabel->setFont( font );
135  mTopLayout->addWidget( mInvalidLabel );
136 
137  // default mode is combobox, no geometric relation and no embed form
138  mLineEdit->hide();
139  mMapIdentificationButton->hide();
140  mHighlightFeatureButton->hide();
141  mAttributeEditorFrame->hide();
142  mInvalidLabel->hide();
143 
144  // connect buttons
145  connect( mOpenFormButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::openForm );
146  connect( mHighlightFeatureButton, &QToolButton::triggered, this, &QgsRelationReferenceWidget::highlightActionTriggered );
147  connect( mMapIdentificationButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::mapIdentification );
148  connect( mRemoveFKButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::deleteForeignKey );
149  connect( mAddEntryButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::addEntry );
150  connect( mComboBox, &QComboBox::editTextChanged, this, &QgsRelationReferenceWidget::updateAddEntryButton );
151  connect( mComboBox, &QgsFeatureListComboBox::modelUpdated, this, &QgsRelationReferenceWidget::updateIndex );
152 }
153 
155 {
156  deleteHighlight();
157  unsetMapTool();
158  delete mMapTool;
159 }
160 
161 void QgsRelationReferenceWidget::updateIndex()
162 {
163  if ( mChainFilters && mComboBox->count() > 0 )
164  {
165  int index = -1;
166 
167  // uninitialized filter
168  if ( ! mFilterComboBoxes.isEmpty()
169  && mFilterComboBoxes[0]->currentIndex() == 0 && mAllowNull )
170  {
171  index = mComboBox->nullIndex();
172  }
173  else if ( mComboBox->count() > mComboBox->nullIndex() )
174  {
175  index = mComboBox->nullIndex() + 1;
176  }
177  else if ( mAllowNull )
178  {
179  index = mComboBox->nullIndex();
180  }
181  else
182  {
183  index = 0;
184  }
185 
186  if ( mComboBox->count() > index )
187  {
188  mComboBox->setCurrentIndex( index );
189  }
190  }
191 }
192 
193 void QgsRelationReferenceWidget::setRelation( const QgsRelation &relation, bool allowNullValue )
194 {
195  mAllowNull = allowNullValue;
196  mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
197 
198  if ( relation.isValid() )
199  {
200  mInvalidLabel->hide();
201 
202  mRelation = relation;
203  mReferencingLayer = relation.referencingLayer();
204  mRelationName = relation.name();
205  mReferencedLayer = relation.referencedLayer();
206  mReferencedField = relation.fieldPairs().at( 0 ).second;
207  if ( mComboBox )
208  mComboBox->setIdentifierField( mReferencedField );
209 
210  mReferencedFieldIdx = mReferencedLayer->fields().lookupField( relation.fieldPairs().at( 0 ).second );
211  mReferencingFieldIdx = mReferencingLayer->fields().lookupField( relation.fieldPairs().at( 0 ).first );
212  mAttributeEditorFrame->setObjectName( QStringLiteral( "referencing/" ) + relation.name() );
213 
214 
215  if ( mEmbedForm )
216  {
218  mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
219  mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
220  mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
221  }
222 
223  connect( mReferencedLayer, &QgsVectorLayer::editingStarted, this, &QgsRelationReferenceWidget::updateAddEntryButton );
224  connect( mReferencedLayer, &QgsVectorLayer::editingStopped, this, &QgsRelationReferenceWidget::updateAddEntryButton );
225  updateAddEntryButton();
226  }
227  else
228  {
229  mInvalidLabel->show();
230  }
231 
232  if ( mShown && isVisible() )
233  {
234  init();
235  }
236 }
237 
239 {
240  if ( !editable )
241  unsetMapTool();
242 
243  mFilterContainer->setEnabled( editable );
244  mComboBox->setEnabled( editable );
245  mComboBox->setEditable( true );
246  mMapIdentificationButton->setEnabled( editable );
247  mRemoveFKButton->setEnabled( editable );
248  mIsEditable = editable;
249 }
250 
251 void QgsRelationReferenceWidget::setForeignKey( const QVariant &value )
252 {
253  if ( !value.isValid() )
254  {
255  return;
256  }
257  if ( value.isNull() )
258  {
260  return;
261  }
262 
263  if ( !mReferencedLayer )
264  return;
265 
266  if ( mReadOnlySelector )
267  {
268  // Attributes from the referencing layer
269  QgsAttributes attrs = QgsAttributes( mReferencingLayer->fields().count() );
270  // Set the value on the foreign key field of the referencing record
271  attrs[ mReferencingLayer->fields().lookupField( mRelation.fieldPairs().at( 0 ).first )] = value;
272 
273  QgsFeatureRequest request = mRelation.getReferencedFeatureRequest( attrs );
274 
275  mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
276 
277  if ( !mFeature.isValid() )
278  {
279  return;
280  }
281 
282  mForeignKey = mFeature.attribute( mReferencedFieldIdx );
283 
284  QgsExpression expr( mReferencedLayer->displayExpression() );
286  context.setFeature( mFeature );
287  QString title = expr.evaluate( &context ).toString();
288  if ( expr.hasEvalError() )
289  {
290  title = mFeature.attribute( mReferencedFieldIdx ).toString();
291  }
292  mLineEdit->setText( title );
293  }
294  else
295  {
296  mComboBox->setIdentifierValue( value );
297 
298  if ( mChainFilters )
299  {
300  QVariant nullValue = QgsApplication::nullRepresentation();
301 
302  QgsFeatureRequest request = mComboBox->currentFeatureRequest();
303 
304  mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
305 
306  const int count = std::min( mFilterComboBoxes.size(), mFilterFields.size() );
307  for ( int i = 0; i < count; i++ )
308  {
309  QVariant v = mFeature.attribute( mFilterFields[i] );
310  QString f = v.isNull() ? nullValue.toString() : v.toString();
311  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
312  }
313  }
314  }
315 
316  mRemoveFKButton->setEnabled( mIsEditable );
317  highlightFeature( mFeature ); // TODO : make this async
318  updateAttributeEditorFrame( mFeature );
319 
320  emitForeignKeyChanged( foreignKey() );
321 }
322 
324 {
325  // deactivate filter comboboxes
326  if ( mChainFilters && !mFilterComboBoxes.isEmpty() )
327  {
328  QComboBox *cb = mFilterComboBoxes.first();
329  cb->setCurrentIndex( 0 );
330  disableChainedComboBoxes( cb );
331  }
332 
333  if ( mReadOnlySelector )
334  {
335  const QString nullValue = QgsApplication::nullRepresentation();
336 
337  QString nullText;
338  if ( mAllowNull )
339  {
340  nullText = tr( "%1 (no selection)" ).arg( nullValue );
341  }
342  mLineEdit->setText( nullText );
343  mForeignKey = QVariant( QVariant::Int );
344  mFeature.setValid( false );
345  }
346  else
347  {
348  mComboBox->setIdentifierValue( QVariant( QVariant::Int ) );
349  }
350  mRemoveFKButton->setEnabled( false );
351  updateAttributeEditorFrame( QgsFeature() );
352  emitForeignKeyChanged( QVariant( QVariant::Int ) );
353 }
354 
356 {
357  QgsFeature f;
358  if ( mReferencedLayer )
359  {
360  QgsFeatureRequest request;
361  if ( mReadOnlySelector )
362  {
363  request = QgsFeatureRequest().setFilterFid( mFeature.id() );
364  }
365  else
366  {
367  request = mComboBox->currentFeatureRequest();
368  }
369  mReferencedLayer->getFeatures( request ).nextFeature( f );
370  }
371  return f;
372 }
373 
375 {
376  if ( mReadOnlySelector )
377  {
378  whileBlocking( mLineEdit )->setText( QString() );
379  }
380  else
381  {
382  whileBlocking( mComboBox )->setIdentifierValue( QVariant() );
383  }
384  mRemoveFKButton->setEnabled( false );
385  updateAttributeEditorFrame( QgsFeature() );
386 }
387 
389 {
390  if ( mReadOnlySelector )
391  {
392  return mForeignKey;
393  }
394  else
395  {
396  return mComboBox->identifierValue();
397  }
398 }
399 
401 {
402  mEditorContext = context;
403  mCanvas = canvas;
404  mMessageBar = messageBar;
405 
406  delete mMapTool;
407  mMapTool = new QgsMapToolIdentifyFeature( mCanvas );
408  mMapTool->setButton( mMapIdentificationButton );
409 }
410 
412 {
413  if ( display )
414  {
415  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
416  mTopLayout->setAlignment( Qt::AlignTop );
417  }
418 
419  mAttributeEditorFrame->setVisible( display );
420  mEmbedForm = display;
421 }
422 
424 {
425  mChooserContainer->setHidden( readOnly );
426  mLineEdit->setVisible( readOnly );
427  mRemoveFKButton->setVisible( mAllowNull && readOnly );
428  mReadOnlySelector = readOnly;
429 }
430 
432 {
433  mHighlightFeatureButton->setVisible( allowMapIdentification );
434  mMapIdentificationButton->setVisible( allowMapIdentification );
435  mAllowMapIdentification = allowMapIdentification;
436 }
437 
439 {
440  mOrderByValue = orderByValue;
441 }
442 
443 void QgsRelationReferenceWidget::setFilterFields( const QStringList &filterFields )
444 {
445  mFilterFields = filterFields;
446 }
447 
449 {
450  mOpenFormButton->setVisible( openFormButtonVisible );
451  mOpenFormButtonVisible = openFormButtonVisible;
452 }
453 
455 {
456  mChainFilters = chainFilters;
457 }
458 
460 {
461  Q_UNUSED( e )
462 
463  mShown = true;
464  if ( !mInitialized )
465  init();
466 }
467 
469 {
470  if ( !mReadOnlySelector && mReferencedLayer )
471  {
472  QApplication::setOverrideCursor( Qt::WaitCursor );
473 
474  QSet<QString> requestedAttrs;
475 
476  if ( !mFilterFields.isEmpty() )
477  {
478  for ( const QString &fieldName : qgis::as_const( mFilterFields ) )
479  {
480  int idx = mReferencedLayer->fields().lookupField( fieldName );
481 
482  if ( idx == -1 )
483  continue;
484 
485  QComboBox *cb = new QComboBox();
486  cb->setProperty( "Field", fieldName );
487  cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
488  mFilterComboBoxes << cb;
489  QVariantList uniqueValues = mReferencedLayer->uniqueValues( idx ).toList();
490  cb->addItem( mReferencedLayer->attributeDisplayName( idx ) );
491  QVariant nullValue = QgsApplication::nullRepresentation();
492  cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->fields().at( idx ).type() ) );
493 
494  std::sort( uniqueValues.begin(), uniqueValues.end(), qgsVariantLessThan );
495  const auto constUniqueValues = uniqueValues;
496  for ( const QVariant &v : constUniqueValues )
497  {
498  cb->addItem( v.toString(), v );
499  }
500 
501  connect( cb, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::filterChanged );
502 
503  // Request this attribute for caching
504  requestedAttrs << fieldName;
505 
506  mFilterLayout->addWidget( cb );
507  }
508 
509  if ( mChainFilters )
510  {
511  QVariant nullValue = QgsApplication::nullRepresentation();
512 
513  QgsFeature ft;
514  QgsFeatureIterator fit = mReferencedLayer->getFeatures();
515  while ( fit.nextFeature( ft ) )
516  {
517  const int count = std::min( mFilterComboBoxes.count(), mFilterFields.count() );
518  for ( int i = 0; i < count - 1; i++ )
519  {
520  QVariant cv = ft.attribute( mFilterFields.at( i ) );
521  QVariant nv = ft.attribute( mFilterFields.at( i + 1 ) );
522  QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
523  QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
524  mFilterCache[mFilterFields[i]][cf] << nf;
525  }
526  }
527 
528  if ( !mFilterComboBoxes.isEmpty() )
529  {
530  QComboBox *cb = mFilterComboBoxes.first();
531  cb->setCurrentIndex( 0 );
532  disableChainedComboBoxes( cb );
533  }
534  }
535  }
536  else
537  {
538  mFilterContainer->hide();
539  }
540 
541  mComboBox->setSourceLayer( mReferencedLayer );
542  mComboBox->setDisplayExpression( mReferencedLayer->displayExpression() );
543  mComboBox->setAllowNull( mAllowNull );
544  mComboBox->setIdentifierField( mReferencedField );
545 
546  QVariant nullValue = QgsApplication::nullRepresentation();
547 
548  if ( mChainFilters && mFeature.isValid() )
549  {
550  for ( int i = 0; i < mFilterFields.size(); i++ )
551  {
552  QVariant v = mFeature.attribute( mFilterFields[i] );
553  QString f = v.isNull() ? nullValue.toString() : v.toString();
554  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
555  }
556  }
557 
558  // Only connect after iterating, to have only one iterator on the referenced table at once
559  connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::comboReferenceChanged );
560  //call it for the first time
561  emit mComboBox->currentIndexChanged( mComboBox->currentIndex() );
562 
563  QApplication::restoreOverrideCursor();
564 
565  mInitialized = true;
566  }
567 }
568 
569 void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
570 {
571  if ( action == mHighlightFeatureAction )
572  {
573  highlightFeature();
574  }
575  else if ( action == mScaleHighlightFeatureAction )
576  {
577  highlightFeature( QgsFeature(), Scale );
578  }
579  else if ( action == mPanHighlightFeatureAction )
580  {
581  highlightFeature( QgsFeature(), Pan );
582  }
583 }
584 
586 {
587  QgsFeature feat = referencedFeature();
588 
589  if ( !feat.isValid() )
590  return;
591 
593  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
594  attributeDialog.exec();
595 }
596 
597 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
598 {
599  if ( !mCanvas )
600  return;
601 
602  if ( !f.isValid() )
603  {
604  f = referencedFeature();
605  if ( !f.isValid() )
606  return;
607  }
608 
609  if ( !f.hasGeometry() )
610  {
611  return;
612  }
613 
614  QgsGeometry geom = f.geometry();
615 
616  // scale or pan
617  if ( canvasExtent == Scale )
618  {
619  QgsRectangle featBBox = geom.boundingBox();
620  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
621  QgsRectangle extent = mCanvas->extent();
622  if ( !extent.contains( featBBox ) )
623  {
624  extent.combineExtentWith( featBBox );
625  extent.scale( 1.1 );
626  mCanvas->setExtent( extent );
627  mCanvas->refresh();
628  }
629  }
630  else if ( canvasExtent == Pan )
631  {
632  QgsGeometry centroid = geom.centroid();
633  QgsPointXY center = centroid.asPoint();
634  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
635  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
636  }
637 
638  // highlight
639  deleteHighlight();
640  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
641  QgsIdentifyMenu::styleHighlight( mHighlight );
642  mHighlight->show();
643 
644  QTimer *timer = new QTimer( this );
645  timer->setSingleShot( true );
646  connect( timer, &QTimer::timeout, this, &QgsRelationReferenceWidget::deleteHighlight );
647  timer->start( 3000 );
648 }
649 
650 void QgsRelationReferenceWidget::deleteHighlight()
651 {
652  if ( mHighlight )
653  {
654  mHighlight->hide();
655  delete mHighlight;
656  }
657  mHighlight = nullptr;
658 }
659 
661 {
662  if ( !mAllowMapIdentification || !mReferencedLayer )
663  return;
664 
665  const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
666  if ( !tools )
667  return;
668  if ( !mCanvas )
669  return;
670 
671  mMapTool->setLayer( mReferencedLayer );
672  mCanvas->setMapTool( mMapTool );
673 
674  mWindowWidget = window();
675 
676  mCanvas->window()->raise();
677  mCanvas->activateWindow();
678  mCanvas->setFocus();
679 
680  connect( mMapTool, static_cast<void ( QgsMapToolIdentifyFeature::* )( const QgsFeature & )>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
681  connect( mMapTool, &QgsMapTool::deactivated, this, &QgsRelationReferenceWidget::mapToolDeactivated );
682 
683  if ( mMessageBar )
684  {
685  QString title = tr( "Relation %1 for %2." ).arg( mRelationName, mReferencingLayer->name() );
686  QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
687  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
688  mMessageBar->pushItem( mMessageBarItem );
689  }
690 }
691 
692 void QgsRelationReferenceWidget::comboReferenceChanged( int index )
693 {
694  Q_UNUSED( index )
695  mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( mFeature );
696  highlightFeature( mFeature );
697  updateAttributeEditorFrame( mFeature );
698 
699  emitForeignKeyChanged( mComboBox->identifierValue() );
700 }
701 
702 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature &feature )
703 {
704  mOpenFormButton->setEnabled( feature.isValid() );
705  // Check if we're running with an embedded frame we need to update
706  if ( mAttributeEditorFrame && mReferencedAttributeForm )
707  {
708  mReferencedAttributeForm->setFeature( feature );
709  }
710 }
711 
713 {
714  return mAllowAddFeatures;
715 }
716 
718 {
719  mAllowAddFeatures = allowAddFeatures;
720  updateAddEntryButton();
721 }
722 
723 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature &feature )
724 {
725  if ( mReadOnlySelector )
726  {
727  QgsExpression expr( mReferencedLayer->displayExpression() );
729  context.setFeature( feature );
730  QString title = expr.evaluate( &context ).toString();
731  if ( expr.hasEvalError() )
732  {
733  title = feature.attribute( mReferencedFieldIdx ).toString();
734  }
735  mLineEdit->setText( title );
736  mForeignKey = feature.attribute( mReferencedFieldIdx );
737  mFeature = feature;
738  }
739  else
740  {
741  mComboBox->setCurrentIndex( mComboBox->findData( feature.attribute( mReferencedFieldIdx ), QgsFeatureFilterModel::Role::IdentifierValueRole ) );
742  mFeature = feature;
743  }
744 
745  mRemoveFKButton->setEnabled( mIsEditable );
746  highlightFeature( feature );
747  updateAttributeEditorFrame( feature );
748  emit foreignKeyChanged( foreignKey() );
749 
750  unsetMapTool();
751 }
752 
753 void QgsRelationReferenceWidget::unsetMapTool()
754 {
755  // deactivate map tool if activated
756  if ( mCanvas && mMapTool )
757  {
758  /* this will call mapToolDeactivated */
759  mCanvas->unsetMapTool( mMapTool );
760  }
761 }
762 
763 void QgsRelationReferenceWidget::mapToolDeactivated()
764 {
765  if ( mWindowWidget )
766  {
767  mWindowWidget->raise();
768  mWindowWidget->activateWindow();
769  }
770 
771  if ( mMessageBar && mMessageBarItem )
772  {
773  mMessageBar->popWidget( mMessageBarItem );
774  }
775  mMessageBarItem = nullptr;
776 }
777 
778 void QgsRelationReferenceWidget::filterChanged()
779 {
780  QVariant nullValue = QgsApplication::nullRepresentation();
781 
782  QMap<QString, QString> filters;
783  QgsAttributeList attrs;
784 
785  QComboBox *scb = qobject_cast<QComboBox *>( sender() );
786 
787  Q_ASSERT( scb );
788 
789  QgsFeature f;
790  QgsFeatureIds featureIds;
791  QString filterExpression;
792 
793  // comboboxes have to be disabled before building filters
794  if ( mChainFilters )
795  disableChainedComboBoxes( scb );
796 
797  // build filters
798  const auto constMFilterComboBoxes = mFilterComboBoxes;
799  for ( QComboBox *cb : constMFilterComboBoxes )
800  {
801  if ( cb->currentIndex() != 0 )
802  {
803  const QString fieldName = cb->property( "Field" ).toString();
804 
805  if ( cb->currentText() == nullValue.toString() )
806  {
807  filters[fieldName] = QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
808  }
809  else
810  {
811  filters[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
812  }
813  attrs << mReferencedLayer->fields().lookupField( fieldName );
814  }
815  }
816 
817  if ( mChainFilters )
818  {
819  QComboBox *ccb = nullptr;
820  const auto constMFilterComboBoxes = mFilterComboBoxes;
821  for ( QComboBox *cb : constMFilterComboBoxes )
822  {
823  if ( !ccb )
824  {
825  if ( cb == scb )
826  ccb = cb;
827 
828  continue;
829  }
830 
831  if ( ccb->currentIndex() != 0 )
832  {
833  const QString fieldName = cb->property( "Field" ).toString();
834 
835  cb->blockSignals( true );
836  cb->clear();
837  cb->addItem( cb->property( "FieldAlias" ).toString() );
838 
839  // ccb = scb
840  // cb = scb + 1
841  QStringList texts;
842  const auto txts { mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] };
843  for ( const QString &txt : txts )
844  {
845  QMap<QString, QString> filtersAttrs = filters;
846  filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt );
847  QString expression = filtersAttrs.values().join( QStringLiteral( " AND " ) );
848 
849  QgsAttributeList subset = attrs;
850  subset << mReferencedLayer->fields().lookupField( fieldName );
851 
852  QgsFeatureIterator it( mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
853 
854  bool found = false;
855  while ( it.nextFeature( f ) )
856  {
857  if ( !featureIds.contains( f.id() ) )
858  featureIds << f.id();
859 
860  found = true;
861  }
862 
863  // item is only provided if at least 1 feature exists
864  if ( found )
865  texts << txt;
866  }
867 
868  texts.sort();
869  cb->addItems( texts );
870 
871  cb->setEnabled( true );
872  cb->blockSignals( false );
873 
874  ccb = cb;
875  }
876  }
877  }
878  filterExpression = filters.values().join( QStringLiteral( " AND " ) );
879  mComboBox->setFilterExpression( filterExpression );
880 }
881 
882 void QgsRelationReferenceWidget::addEntry()
883 {
884  QgsFeature f( mReferencedLayer->fields() );
885  QgsAttributeMap attributes;
886 
887  // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
888  if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
889  {
890  int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
891 
892  if ( fieldIdx != -1 )
893  {
894  attributes.insert( fieldIdx, mComboBox->currentText() );
895  }
896  }
897 
898  if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, QgsGeometry(), &f ) )
899  {
900  mComboBox->setIdentifierValue( f.attribute( mReferencingFieldIdx ) );
901  mAddEntryButton->setEnabled( false );
902  }
903 }
904 
905 void QgsRelationReferenceWidget::updateAddEntryButton()
906 {
907  mAddEntryButton->setVisible( mAllowAddFeatures );
908  mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
909 }
910 
911 void QgsRelationReferenceWidget::disableChainedComboBoxes( const QComboBox *scb )
912 {
913  QComboBox *ccb = nullptr;
914  const auto constMFilterComboBoxes = mFilterComboBoxes;
915  for ( QComboBox *cb : constMFilterComboBoxes )
916  {
917  if ( !ccb )
918  {
919  if ( cb == scb )
920  {
921  ccb = cb;
922  }
923 
924  continue;
925  }
926 
927  cb->setCurrentIndex( 0 );
928  if ( ccb->currentIndex() == 0 )
929  {
930  cb->setEnabled( false );
931  }
932 
933  ccb = cb;
934  }
935 }
936 
937 void QgsRelationReferenceWidget::emitForeignKeyChanged( const QVariant &foreignKey )
938 {
939  if ( foreignKey != mForeignKey || foreignKey.isNull() != mForeignKey.isNull() )
940  {
941  mForeignKey = foreignKey;
942  emit foreignKeyChanged( foreignKey );
943  }
944 }
void unsetMapTool(QgsMapTool *mapTool)
Unset the current map tool or last non zoom tool.
int lookupField(const QString &fieldName) const
Looks up field&#39;s index from the field name.
Definition: qgsfields.cpp:324
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) ...
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:183
Class for parsing and evaluation of expressions (formerly called "search strings").
void setIdentifierValue(const QVariant &identifierValue)
The identifier value of the currently selected feature.
const QgsVectorLayerTools * vectorLayerTools() const
Returns the associated vector layer tools.
QString name
Definition: qgsrelation.h:48
QgsFeatureId id
Definition: qgsfeature.h:64
QVariant foreignKey() const
returns the related feature foreign key
Wrapper for iterator of features from vector data provider or vector layer.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:342
This offers a combobox with autocompleter that allows selecting features from a layer.
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QgsFeatureRequest currentFeatureRequest() const
Shorthand for getting a feature request to query the currently selected feature.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:34
int nullIndex() const
Returns the current index of the NULL value, or -1 if NULL values are not allowed.
void setLayer(QgsVectorLayer *vl)
change the layer used by the map tool to identify
QgsFeature referencedFeature() const
Returns the related feature (from the referenced layer) if no feature is related, it returns an inval...
bool chainFilters() const
Determines if the filters are chained.
A groupbox that collapses/expands when toggled and can save its collapsed and checked states...
void setFilterFields(const QStringList &filterFields)
Sets the fields for which filter comboboxes will be created.
void deleteForeignKey()
unset the currently related feature
void foreignKeyChanged(const QVariant &)
This class contains context information for attribute editor widgets.
A class to represent a 2D point.
Definition: qgspointxy.h:43
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
Definition: qgsrectangle.h:235
void setOpenFormButtonVisible(bool openFormButtonVisible)
QgsVectorLayer referencingLayer
Definition: qgsrelation.h:46
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:45
bool allowAddFeatures() const
Determines if a button for adding new features should be shown.
void refresh()
Repaints the canvas map.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:111
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
bool orderByValue()
If the widget will order the combobox entries by value.
void setSourceLayer(QgsVectorLayer *sourceLayer)
The layer from which features should be listed.
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:197
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:73
bool qgsVariantLessThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is less than the second.
Definition: qgis.cpp:153
void setAllowMapIdentification(bool allowMapIdentification)
bool allowMapIdentification()
determines if the widget offers the possibility to select the related feature on the map (using a ded...
void setButton(QAbstractButton *button)
Use this to associate a button to this maptool.
Definition: qgsmaptool.cpp:139
void setForeignKey(const QVariant &value)
this sets the related feature using from the foreign key
QgsField at(int i) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:163
void setOrderByValue(bool orderByValue)
Sets if the widget will order the combobox entries by value.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
QgsFeatureRequest & setFilterFid(QgsFeatureId fid)
Sets feature ID that should be fetched.
static QgsMessageBarItem * createMessage(const QString &text, QWidget *parent=nullptr)
make out a widget containing a message to be displayed on the bar
void setMapTool(QgsMapTool *mapTool, bool clean=false)
Sets the map tool currently being used on the canvas.
QgsFeatureRequest getReferencedFeatureRequest(const QgsAttributes &attributes) const
Creates a request to return the feature on the referenced (parent) layer which is referenced by the p...
QgsFields fields() const FINAL
Returns the list of fields of this layer.
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 setAllowNull(bool allowNull)
Determines if a NULL value should be available in the list.
void showEvent(QShowEvent *e) override
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:38
QgsRectangle extent() const
Returns the current zoom extent of the map canvas.
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
A class for highlight features on the map.
Definition: qgshighlight.h:49
This class wraps a request for features to a vector layer (or directly its vector data provider)...
static QString nullRepresentation()
This string is used to represent the value NULL throughout QGIS.
QgsVectorLayer referencedLayer
Definition: qgsrelation.h:47
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer&#39;s project and layer.
void setRelation(const QgsRelation &relation, bool allowNullValue)
void editingStarted()
Emitted when editing on this layer has started.
static QString createFieldEqualityExpression(const QString &fieldName, const QVariant &value)
Create an expression allowing to evaluate if a field is equal to a value.
QString displayExpression
void zoomByFactor(double scaleFactor, const QgsPointXY *center=nullptr)
Zoom with the factor supplied.
QList< QgsRelation::FieldPair > fieldPairs() const
Returns the field pairs which form this relation The first element of each pair are the field names o...
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle...
Definition: qgsrectangle.h:359
The QgsMapToolIdentifyFeature class is a map tool to identify a feature on a chosen layer...
void setFilterExpression(const QString &filterExpression)
An additional expression to further restrict the available features.
void setValid(bool validity)
Sets the validity of the feature.
Definition: qgsfeature.cpp:188
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:212
void deactivated()
signal emitted once the map tool is deactivated
void pushItem(QgsMessageBarItem *item)
Display a message item on the bar after hiding the currently visible one and putting it in a stack...
bool isValid
Definition: qgsrelation.h:49
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
void featureIdentified(const QgsFeature &)
QgsPointXY layerToMapCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from layer&#39;s CRS to output CRS
void setAllowAddFeatures(bool allowAddFeatures)
Determines if a button for adding new features should be shown.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
void showIndeterminateState()
Sets the widget to display in an indeterminate "mixed value" state.
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.
void setExtent(const QgsRectangle &r, bool magnified=false)
Sets the extent of the map canvas.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QString name
Definition: qgsmaplayer.h:82
static void styleHighlight(QgsHighlight *highlight)
Applies style from the settings to the highlight.
QgsGeometry geometry
Definition: qgsfeature.h:67
QList< int > QgsAttributeList
Definition: qgsfield.h:27
bool nextFeature(QgsFeature &f)
A vector of attributes.
Definition: qgsattributes.h:57
void setDisplayExpression(const QString &displayExpression)
The display expression will be used to display features as well as the value to match the typed text ...
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
void mapIdentification()
activate the map tool to select a new related feature on the map
QString attributeDisplayName(int index) const
Convenience function that returns the attribute alias if defined or the field name else...
void modelUpdated()
The underlying model has been updated.
QVariant::Type type
Definition: qgsfield.h:56
bool openFormButtonVisible()
determines the open form button is visible in the widget
void openForm()
open the form of the related feature in a new dialog
void setIdentifierField(const QString &identifierField)
Field name that will be used to uniquely identify the current feature.
void setChainFilters(bool chainFilters)
Set if filters are chained.
A form was embedded as a widget on another form.