QGIS API Documentation  3.6.0-Noosa (5873452)
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 
45 
47  : QWidget( parent )
48 {
49  mTopLayout = new QVBoxLayout( this );
50  mTopLayout->setContentsMargins( 0, 0, 0, 0 );
51 
52  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::Fixed );
53 
54  setLayout( mTopLayout );
55 
56  QHBoxLayout *editLayout = new QHBoxLayout();
57  editLayout->setContentsMargins( 0, 0, 0, 0 );
58  editLayout->setSpacing( 2 );
59 
60  // Prepare the container and layout for the filter comboboxes
61  mChooserContainer = new QWidget;
62  editLayout->addWidget( mChooserContainer );
63  QHBoxLayout *chooserLayout = new QHBoxLayout;
64  chooserLayout->setContentsMargins( 0, 0, 0, 0 );
65  mFilterLayout = new QHBoxLayout;
66  mFilterLayout->setContentsMargins( 0, 0, 0, 0 );
67  mFilterContainer = new QWidget;
68  mFilterContainer->setLayout( mFilterLayout );
69  mChooserContainer->setLayout( chooserLayout );
70  chooserLayout->addWidget( mFilterContainer );
71 
72  mComboBox = new QgsFeatureListComboBox();
73  mChooserContainer->layout()->addWidget( mComboBox );
74 
75  // read-only line edit
76  mLineEdit = new QLineEdit();
77  mLineEdit->setReadOnly( true );
78  editLayout->addWidget( mLineEdit );
79 
80  // open form button
81  mOpenFormButton = new QToolButton();
82  mOpenFormButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPropertyItem.svg" ) ) );
83  mOpenFormButton->setText( tr( "Open Related Feature Form" ) );
84  editLayout->addWidget( mOpenFormButton );
85 
86  mAddEntryButton = new QToolButton();
87  mAddEntryButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAdd.svg" ) ) );
88  mAddEntryButton->setText( tr( "Add New Entry" ) );
89  editLayout->addWidget( mAddEntryButton );
90 
91  // highlight button
92  mHighlightFeatureButton = new QToolButton( this );
93  mHighlightFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
94  mHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHighlightFeature.svg" ) ), tr( "Highlight feature" ), this );
95  mScaleHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleHighlightFeature.svg" ) ), tr( "Scale and highlight feature" ), this );
96  mPanHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPanHighlightFeature.svg" ) ), tr( "Pan and highlight feature" ), this );
97  mHighlightFeatureButton->addAction( mHighlightFeatureAction );
98  mHighlightFeatureButton->addAction( mScaleHighlightFeatureAction );
99  mHighlightFeatureButton->addAction( mPanHighlightFeatureAction );
100  mHighlightFeatureButton->setDefaultAction( mHighlightFeatureAction );
101  editLayout->addWidget( mHighlightFeatureButton );
102 
103  // map identification button
104  mMapIdentificationButton = new QToolButton( this );
105  mMapIdentificationButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMapIdentification.svg" ) ) );
106  mMapIdentificationButton->setText( tr( "Select on Map" ) );
107  mMapIdentificationButton->setCheckable( true );
108  editLayout->addWidget( mMapIdentificationButton );
109 
110  // remove foreign key button
111  mRemoveFKButton = new QToolButton( this );
112  mRemoveFKButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemove.svg" ) ) );
113  mRemoveFKButton->setText( tr( "No Selection" ) );
114  editLayout->addWidget( mRemoveFKButton );
115 
116  // add line to top layout
117  mTopLayout->addLayout( editLayout );
118 
119  // embed form
120  mAttributeEditorFrame = new QgsCollapsibleGroupBox( this );
121  mAttributeEditorLayout = new QVBoxLayout( mAttributeEditorFrame );
122  mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
123  mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
124  mTopLayout->addWidget( mAttributeEditorFrame );
125 
126  // invalid label
127  mInvalidLabel = new QLabel( tr( "The relation is not valid. Please make sure your relation definitions are OK." ) );
128  mInvalidLabel->setWordWrap( true );
129  QFont font = mInvalidLabel->font();
130  font.setItalic( true );
131  mInvalidLabel->setStyleSheet( QStringLiteral( "QLabel { color: red; } " ) );
132  mInvalidLabel->setFont( font );
133  mTopLayout->addWidget( mInvalidLabel );
134 
135  // default mode is combobox, no geometric relation and no embed form
136  mLineEdit->hide();
137  mMapIdentificationButton->hide();
138  mHighlightFeatureButton->hide();
139  mAttributeEditorFrame->hide();
140  mInvalidLabel->hide();
141 
142  // connect buttons
143  connect( mOpenFormButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::openForm );
144  connect( mHighlightFeatureButton, &QToolButton::triggered, this, &QgsRelationReferenceWidget::highlightActionTriggered );
145  connect( mMapIdentificationButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::mapIdentification );
146  connect( mRemoveFKButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::deleteForeignKey );
147  connect( mAddEntryButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::addEntry );
148  connect( mComboBox, &QComboBox::editTextChanged, this, &QgsRelationReferenceWidget::updateAddEntryButton );
149  connect( mComboBox, &QgsFeatureListComboBox::modelUpdated, this, &QgsRelationReferenceWidget::updateIndex );
150 }
151 
153 {
154  deleteHighlight();
155  unsetMapTool();
156  delete mMapTool;
157 }
158 
159 void QgsRelationReferenceWidget::updateIndex()
160 {
161  if ( mChainFilters && mComboBox->count() > 0 )
162  {
163  int index = -1;
164 
165  // uninitialized filter
166  if ( ! mFilterComboBoxes.isEmpty()
167  && mFilterComboBoxes[0]->currentIndex() == 0 && mAllowNull )
168  {
169  index = mComboBox->nullIndex();
170  }
171  else if ( mComboBox->count() > mComboBox->nullIndex() )
172  {
173  index = mComboBox->nullIndex() + 1;
174  }
175  else if ( mAllowNull )
176  {
177  index = mComboBox->nullIndex();
178  }
179  else
180  {
181  index = 0;
182  }
183 
184  if ( mComboBox->count() > index )
185  {
186  mComboBox->setCurrentIndex( index );
187  }
188  }
189 }
190 
191 void QgsRelationReferenceWidget::setRelation( const QgsRelation &relation, bool allowNullValue )
192 {
193  mAllowNull = allowNullValue;
194  mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
195 
196  if ( relation.isValid() )
197  {
198  mInvalidLabel->hide();
199 
200  mRelation = relation;
201  mReferencingLayer = relation.referencingLayer();
202  mRelationName = relation.name();
203  mReferencedLayer = relation.referencedLayer();
204  mReferencedField = relation.fieldPairs().at( 0 ).second;
205  if ( mComboBox )
206  mComboBox->setIdentifierField( mReferencedField );
207 
208  mReferencedFieldIdx = mReferencedLayer->fields().lookupField( relation.fieldPairs().at( 0 ).second );
209  mReferencingFieldIdx = mReferencingLayer->fields().lookupField( relation.fieldPairs().at( 0 ).first );
210  mAttributeEditorFrame->setObjectName( QStringLiteral( "referencing/" ) + relation.name() );
211 
212 
213  if ( mEmbedForm )
214  {
216  mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
217  mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
218  mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
219  }
220 
221  connect( mReferencedLayer, &QgsVectorLayer::editingStarted, this, &QgsRelationReferenceWidget::updateAddEntryButton );
222  connect( mReferencedLayer, &QgsVectorLayer::editingStopped, this, &QgsRelationReferenceWidget::updateAddEntryButton );
223  updateAddEntryButton();
224  }
225  else
226  {
227  mInvalidLabel->show();
228  }
229 
230  if ( mShown && isVisible() )
231  {
232  init();
233  }
234 }
235 
237 {
238  if ( !editable )
239  unsetMapTool();
240 
241  mFilterContainer->setEnabled( editable );
242  mComboBox->setEnabled( editable );
243  mComboBox->setEditable( true );
244  mMapIdentificationButton->setEnabled( editable );
245  mRemoveFKButton->setEnabled( editable );
246  mIsEditable = editable;
247 }
248 
249 void QgsRelationReferenceWidget::setForeignKey( const QVariant &value )
250 {
251  if ( !value.isValid() )
252  {
253  return;
254  }
255  if ( value.isNull() )
256  {
258  return;
259  }
260 
261  if ( !mReferencedLayer )
262  return;
263 
264  if ( mReadOnlySelector )
265  {
266  // Attributes from the referencing layer
267  QgsAttributes attrs = QgsAttributes( mReferencingLayer->fields().count() );
268  // Set the value on the foreign key field of the referencing record
269  attrs[ mReferencingLayer->fields().lookupField( mRelation.fieldPairs().at( 0 ).first )] = value;
270 
271  QgsFeatureRequest request = mRelation.getReferencedFeatureRequest( attrs );
272 
273  mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
274 
275  if ( !mFeature.isValid() )
276  {
277  return;
278  }
279 
280  mForeignKey = mFeature.attribute( mReferencedFieldIdx );
281 
282  QgsExpression expr( mReferencedLayer->displayExpression() );
284  context.setFeature( mFeature );
285  QString title = expr.evaluate( &context ).toString();
286  if ( expr.hasEvalError() )
287  {
288  title = mFeature.attribute( mReferencedFieldIdx ).toString();
289  }
290  mLineEdit->setText( title );
291  }
292  else
293  {
294  mComboBox->setIdentifierValue( value );
295 
296  if ( mChainFilters )
297  {
298  QVariant nullValue = QgsApplication::nullRepresentation();
299 
300  QgsFeatureRequest request = mComboBox->currentFeatureRequest();
301 
302  mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
303 
304  const int count = std::min( mFilterComboBoxes.size(), mFilterFields.size() );
305  for ( int i = 0; i < count; i++ )
306  {
307  QVariant v = mFeature.attribute( mFilterFields[i] );
308  QString f = v.isNull() ? nullValue.toString() : v.toString();
309  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
310  }
311  }
312  }
313 
314  mRemoveFKButton->setEnabled( mIsEditable );
315  highlightFeature( mFeature ); // TODO : make this async
316  updateAttributeEditorFrame( mFeature );
317 
318  emitForeignKeyChanged( foreignKey() );
319 }
320 
322 {
323  // deactivate filter comboboxes
324  if ( mChainFilters && !mFilterComboBoxes.isEmpty() )
325  {
326  QComboBox *cb = mFilterComboBoxes.first();
327  cb->setCurrentIndex( 0 );
328  disableChainedComboBoxes( cb );
329  }
330 
331  if ( mReadOnlySelector )
332  {
333  const QString nullValue = QgsApplication::nullRepresentation();
334 
335  QString nullText;
336  if ( mAllowNull )
337  {
338  nullText = tr( "%1 (no selection)" ).arg( nullValue );
339  }
340  mLineEdit->setText( nullText );
341  mForeignKey = QVariant( QVariant::Int );
342  mFeature.setValid( false );
343  }
344  else
345  {
346  mComboBox->setIdentifierValue( QVariant( QVariant::Int ) );
347  }
348  mRemoveFKButton->setEnabled( false );
349  updateAttributeEditorFrame( QgsFeature() );
350  emitForeignKeyChanged( QVariant( QVariant::Int ) );
351 }
352 
354 {
355  QgsFeature f;
356  if ( mReferencedLayer )
357  {
358  QgsFeatureRequest request;
359  if ( mReadOnlySelector )
360  {
361  request = QgsFeatureRequest().setFilterFid( mFeature.id() );
362  }
363  else
364  {
365  request = mComboBox->currentFeatureRequest();
366  }
367  mReferencedLayer->getFeatures( request ).nextFeature( f );
368  }
369  return f;
370 }
371 
373 {
374  if ( mReadOnlySelector )
375  {
376  whileBlocking( mLineEdit )->setText( QString() );
377  }
378  else
379  {
380  whileBlocking( mComboBox )->setIdentifierValue( QVariant() );
381  }
382  mRemoveFKButton->setEnabled( false );
383  updateAttributeEditorFrame( QgsFeature() );
384 }
385 
387 {
388  if ( mReadOnlySelector )
389  {
390  return mForeignKey;
391  }
392  else
393  {
394  return mComboBox->identifierValue();
395  }
396 }
397 
399 {
400  mEditorContext = context;
401  mCanvas = canvas;
402  mMessageBar = messageBar;
403 
404  delete mMapTool;
405  mMapTool = new QgsMapToolIdentifyFeature( mCanvas );
406  mMapTool->setButton( mMapIdentificationButton );
407 }
408 
410 {
411  if ( display )
412  {
413  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
414  mTopLayout->setAlignment( Qt::AlignTop );
415  }
416 
417  mAttributeEditorFrame->setVisible( display );
418  mEmbedForm = display;
419 }
420 
422 {
423  mChooserContainer->setHidden( readOnly );
424  mLineEdit->setVisible( readOnly );
425  mRemoveFKButton->setVisible( mAllowNull && readOnly );
426  mReadOnlySelector = readOnly;
427 }
428 
430 {
431  mHighlightFeatureButton->setVisible( allowMapIdentification );
432  mMapIdentificationButton->setVisible( allowMapIdentification );
433  mAllowMapIdentification = allowMapIdentification;
434 }
435 
437 {
438  mOrderByValue = orderByValue;
439 }
440 
441 void QgsRelationReferenceWidget::setFilterFields( const QStringList &filterFields )
442 {
443  mFilterFields = filterFields;
444 }
445 
447 {
448  mOpenFormButton->setVisible( openFormButtonVisible );
449  mOpenFormButtonVisible = openFormButtonVisible;
450 }
451 
453 {
454  mChainFilters = chainFilters;
455 }
456 
458 {
459  Q_UNUSED( e )
460 
461  mShown = true;
462  if ( !mInitialized )
463  init();
464 }
465 
467 {
468  if ( !mReadOnlySelector && mReferencedLayer )
469  {
470  QApplication::setOverrideCursor( Qt::WaitCursor );
471 
472  QSet<QString> requestedAttrs;
473 
474  if ( !mFilterFields.isEmpty() )
475  {
476  for ( const QString &fieldName : qgis::as_const( mFilterFields ) )
477  {
478  int idx = mReferencedLayer->fields().lookupField( fieldName );
479 
480  if ( idx == -1 )
481  continue;
482 
483  QComboBox *cb = new QComboBox();
484  cb->setProperty( "Field", fieldName );
485  cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
486  mFilterComboBoxes << cb;
487  QVariantList uniqueValues = mReferencedLayer->uniqueValues( idx ).toList();
488  cb->addItem( mReferencedLayer->attributeDisplayName( idx ) );
489  QVariant nullValue = QgsApplication::nullRepresentation();
490  cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->fields().at( idx ).type() ) );
491 
492  std::sort( uniqueValues.begin(), uniqueValues.end(), qgsVariantLessThan );
493  Q_FOREACH ( const QVariant &v, uniqueValues )
494  {
495  cb->addItem( v.toString(), v );
496  }
497 
498  connect( cb, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::filterChanged );
499 
500  // Request this attribute for caching
501  requestedAttrs << fieldName;
502 
503  mFilterLayout->addWidget( cb );
504  }
505 
506  if ( mChainFilters )
507  {
508  QVariant nullValue = QgsApplication::nullRepresentation();
509 
510  QgsFeature ft;
511  QgsFeatureIterator fit = mReferencedLayer->getFeatures();
512  while ( fit.nextFeature( ft ) )
513  {
514  const int count = std::min( mFilterComboBoxes.count(), mFilterFields.count() );
515  for ( int i = 0; i < count - 1; i++ )
516  {
517  QVariant cv = ft.attribute( mFilterFields.at( i ) );
518  QVariant nv = ft.attribute( mFilterFields.at( i + 1 ) );
519  QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
520  QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
521  mFilterCache[mFilterFields[i]][cf] << nf;
522  }
523  }
524 
525  if ( !mFilterComboBoxes.isEmpty() )
526  {
527  QComboBox *cb = mFilterComboBoxes.first();
528  cb->setCurrentIndex( 0 );
529  disableChainedComboBoxes( cb );
530  }
531  }
532  }
533  else
534  {
535  mFilterContainer->hide();
536  }
537 
538  mComboBox->setSourceLayer( mReferencedLayer );
539  mComboBox->setDisplayExpression( mReferencedLayer->displayExpression() );
540  mComboBox->setAllowNull( mAllowNull );
541  mComboBox->setIdentifierField( mReferencedField );
542 
543  QVariant nullValue = QgsApplication::nullRepresentation();
544 
545  if ( mChainFilters && mFeature.isValid() )
546  {
547  for ( int i = 0; i < mFilterFields.size(); i++ )
548  {
549  QVariant v = mFeature.attribute( mFilterFields[i] );
550  QString f = v.isNull() ? nullValue.toString() : v.toString();
551  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
552  }
553  }
554 
555  // Only connect after iterating, to have only one iterator on the referenced table at once
556  connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::comboReferenceChanged );
557  //call it for the first time
558  emit mComboBox->currentIndexChanged( mComboBox->currentIndex() );
559 
560  QApplication::restoreOverrideCursor();
561 
562  mInitialized = true;
563  }
564 }
565 
566 void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
567 {
568  if ( action == mHighlightFeatureAction )
569  {
570  highlightFeature();
571  }
572  else if ( action == mScaleHighlightFeatureAction )
573  {
574  highlightFeature( QgsFeature(), Scale );
575  }
576  else if ( action == mPanHighlightFeatureAction )
577  {
578  highlightFeature( QgsFeature(), Pan );
579  }
580 }
581 
583 {
584  QgsFeature feat = referencedFeature();
585 
586  if ( !feat.isValid() )
587  return;
588 
590  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
591  attributeDialog.exec();
592 }
593 
594 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
595 {
596  if ( !mCanvas )
597  return;
598 
599  if ( !f.isValid() )
600  {
601  f = referencedFeature();
602  if ( !f.isValid() )
603  return;
604  }
605 
606  if ( !f.hasGeometry() )
607  {
608  return;
609  }
610 
611  QgsGeometry geom = f.geometry();
612 
613  // scale or pan
614  if ( canvasExtent == Scale )
615  {
616  QgsRectangle featBBox = geom.boundingBox();
617  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
618  QgsRectangle extent = mCanvas->extent();
619  if ( !extent.contains( featBBox ) )
620  {
621  extent.combineExtentWith( featBBox );
622  extent.scale( 1.1 );
623  mCanvas->setExtent( extent );
624  mCanvas->refresh();
625  }
626  }
627  else if ( canvasExtent == Pan )
628  {
629  QgsGeometry centroid = geom.centroid();
630  QgsPointXY center = centroid.asPoint();
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  QgsSettings settings;
639  QColor color = QColor( settings.value( QStringLiteral( "Map/highlight/color" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
640  int alpha = settings.value( QStringLiteral( "Map/highlight/colorAlpha" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
641  double buffer = settings.value( QStringLiteral( "Map/highlight/buffer" ), Qgis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
642  double minWidth = settings.value( QStringLiteral( "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, &QTimer::timeout, this, &QgsRelationReferenceWidget::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, static_cast<void ( QgsMapToolIdentifyFeature::* )( const QgsFeature & )>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
688  connect( mMapTool, &QgsMapTool::deactivated, this, &QgsRelationReferenceWidget::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 {
701  Q_UNUSED( index )
702  mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( mFeature );
703  highlightFeature( mFeature );
704  updateAttributeEditorFrame( mFeature );
705 
706  emitForeignKeyChanged( mComboBox->identifierValue() );
707 }
708 
709 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature &feature )
710 {
711  mOpenFormButton->setEnabled( feature.isValid() );
712  // Check if we're running with an embedded frame we need to update
713  if ( mAttributeEditorFrame && mReferencedAttributeForm )
714  {
715  mReferencedAttributeForm->setFeature( feature );
716  }
717 }
718 
720 {
721  return mAllowAddFeatures;
722 }
723 
725 {
726  mAllowAddFeatures = allowAddFeatures;
727  updateAddEntryButton();
728 }
729 
730 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature &feature )
731 {
732  if ( mReadOnlySelector )
733  {
734  QgsExpression expr( mReferencedLayer->displayExpression() );
736  context.setFeature( feature );
737  QString title = expr.evaluate( &context ).toString();
738  if ( expr.hasEvalError() )
739  {
740  title = feature.attribute( mReferencedFieldIdx ).toString();
741  }
742  mLineEdit->setText( title );
743  mForeignKey = feature.attribute( mReferencedFieldIdx );
744  mFeature = feature;
745  }
746  else
747  {
748  mComboBox->setCurrentIndex( mComboBox->findData( feature.id(), QgsAttributeTableModel::FeatureIdRole ) );
749  mFeature = feature;
750  }
751 
752  mRemoveFKButton->setEnabled( mIsEditable );
753  highlightFeature( feature );
754  updateAttributeEditorFrame( feature );
755  emit foreignKeyChanged( foreignKey() );
756 
757  unsetMapTool();
758 }
759 
760 void QgsRelationReferenceWidget::unsetMapTool()
761 {
762  // deactivate map tool if activated
763  if ( mCanvas && mMapTool )
764  {
765  /* this will call mapToolDeactivated */
766  mCanvas->unsetMapTool( mMapTool );
767  }
768 }
769 
770 void QgsRelationReferenceWidget::mapToolDeactivated()
771 {
772  if ( mWindowWidget )
773  {
774  mWindowWidget->raise();
775  mWindowWidget->activateWindow();
776  }
777 
778  if ( mMessageBar && mMessageBarItem )
779  {
780  mMessageBar->popWidget( mMessageBarItem );
781  }
782  mMessageBarItem = nullptr;
783 }
784 
785 void QgsRelationReferenceWidget::filterChanged()
786 {
787  QVariant nullValue = QgsApplication::nullRepresentation();
788 
789  QMap<QString, QString> filters;
790  QgsAttributeList attrs;
791 
792  QComboBox *scb = qobject_cast<QComboBox *>( sender() );
793 
794  Q_ASSERT( scb );
795 
796  QgsFeature f;
797  QgsFeatureIds featureIds;
798  QString filterExpression;
799 
800  // comboboxes have to be disabled before building filters
801  if ( mChainFilters )
802  disableChainedComboBoxes( scb );
803 
804  // build filters
805  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
806  {
807  if ( cb->currentIndex() != 0 )
808  {
809  const QString fieldName = cb->property( "Field" ).toString();
810 
811  if ( cb->currentText() == nullValue.toString() )
812  {
813  filters[fieldName] = QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
814  }
815  else
816  {
817  filters[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
818  }
819  attrs << mReferencedLayer->fields().lookupField( fieldName );
820  }
821  }
822 
823  if ( mChainFilters )
824  {
825  QComboBox *ccb = nullptr;
826  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
827  {
828  if ( !ccb )
829  {
830  if ( cb == scb )
831  ccb = cb;
832 
833  continue;
834  }
835 
836  if ( ccb->currentIndex() != 0 )
837  {
838  const QString fieldName = cb->property( "Field" ).toString();
839 
840  cb->blockSignals( true );
841  cb->clear();
842  cb->addItem( cb->property( "FieldAlias" ).toString() );
843 
844  // ccb = scb
845  // cb = scb + 1
846  QStringList texts;
847  Q_FOREACH ( const QString &txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
848  {
849  QMap<QString, QString> filtersAttrs = filters;
850  filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt );
851  QString expression = filtersAttrs.values().join( QStringLiteral( " AND " ) );
852 
853  QgsAttributeList subset = attrs;
854  subset << mReferencedLayer->fields().lookupField( fieldName );
855 
856  QgsFeatureIterator it( mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
857 
858  bool found = false;
859  while ( it.nextFeature( f ) )
860  {
861  if ( !featureIds.contains( f.id() ) )
862  featureIds << f.id();
863 
864  found = true;
865  }
866 
867  // item is only provided if at least 1 feature exists
868  if ( found )
869  texts << txt;
870  }
871 
872  texts.sort();
873  cb->addItems( texts );
874 
875  cb->setEnabled( true );
876  cb->blockSignals( false );
877 
878  ccb = cb;
879  }
880  }
881  }
882  filterExpression = filters.values().join( QStringLiteral( " AND " ) );
883  mComboBox->setFilterExpression( filterExpression );
884 }
885 
886 void QgsRelationReferenceWidget::addEntry()
887 {
888  QgsFeature f( mReferencedLayer->fields() );
889  QgsAttributeMap attributes;
890 
891  // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
892  if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
893  {
894  int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
895 
896  if ( fieldIdx != -1 )
897  {
898  attributes.insert( fieldIdx, mComboBox->currentText() );
899  }
900  }
901 
902  if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, QgsGeometry(), &f ) )
903  {
904  mComboBox->setIdentifierValue( f.attribute( mReferencingFieldIdx ) );
905  mAddEntryButton->setEnabled( false );
906  }
907 }
908 
909 void QgsRelationReferenceWidget::updateAddEntryButton()
910 {
911  mAddEntryButton->setVisible( mAllowAddFeatures );
912  mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
913 }
914 
915 void QgsRelationReferenceWidget::disableChainedComboBoxes( const QComboBox *scb )
916 {
917  QComboBox *ccb = nullptr;
918  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
919  {
920  if ( !ccb )
921  {
922  if ( cb == scb )
923  {
924  ccb = cb;
925  }
926 
927  continue;
928  }
929 
930  cb->setCurrentIndex( 0 );
931  if ( ccb->currentIndex() == 0 )
932  {
933  cb->setEnabled( false );
934  }
935 
936  ccb = cb;
937  }
938 }
939 
940 void QgsRelationReferenceWidget::emitForeignKeyChanged( const QVariant &foreignKey )
941 {
942  if ( foreignKey != mForeignKey || foreignKey.isNull() != mForeignKey.isNull() )
943  {
944  mForeignKey = foreignKey;
945  emit foreignKeyChanged( foreignKey );
946  }
947 }
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.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
A groupbox that collapses/expands when toggled and can save its collapsed and checked states...
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
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
void setFillColor(const QColor &fillColor)
Fill color for the highlight.
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:45
static const QColor DEFAULT_HIGHLIGHT_COLOR
Default highlight color.
Definition: qgis.h:109
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:106
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 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:138
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
Get the feature id of the feature in this row.
void setBuffer(double buffer)
Set line / stroke buffer in millimeters.
Definition: qgshighlight.h:127
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()
Is emitted, when edited changes successfully have been 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()
Is 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 setColor(const QColor &color)
Set line/stroke to color, polygon fill to color with alpha = 63.
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.
static const double DEFAULT_HIGHLIGHT_MIN_WIDTH_MM
Default highlight line/stroke minimum width in mm.
Definition: qgis.h:119
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Query the layer for features specified in request.
QString name
Definition: qgsmaplayer.h:68
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
static const double DEFAULT_HIGHLIGHT_BUFFER_MM
Default highlight buffer in mm.
Definition: qgis.h:114
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.
void setMinWidth(double width)
Set minimum line / stroke width in millimeters.
Definition: qgshighlight.h:134