QGIS API Documentation  3.2.0-Bonn (bc43194)
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"
43 
44 
46  : QWidget( parent )
47 {
48  mTopLayout = new QVBoxLayout( this );
49  mTopLayout->setContentsMargins( 0, 0, 0, 0 );
50 
51  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::Fixed );
52 
53  setLayout( mTopLayout );
54 
55  QHBoxLayout *editLayout = new QHBoxLayout();
56  editLayout->setContentsMargins( 0, 0, 0, 0 );
57  editLayout->setSpacing( 2 );
58 
59  // Prepare the container and layout for the filter comboboxes
60  mChooserContainer = new QWidget;
61  editLayout->addWidget( mChooserContainer );
62  QHBoxLayout *chooserLayout = new QHBoxLayout;
63  chooserLayout->setContentsMargins( 0, 0, 0, 0 );
64  mFilterLayout = new QHBoxLayout;
65  mFilterLayout->setContentsMargins( 0, 0, 0, 0 );
66  mFilterContainer = new QWidget;
67  mFilterContainer->setLayout( mFilterLayout );
68  mChooserContainer->setLayout( chooserLayout );
69  chooserLayout->addWidget( mFilterContainer );
70 
71  mComboBox = new QgsFeatureListComboBox();
72  mChooserContainer->layout()->addWidget( mComboBox );
73 
74  // read-only line edit
75  mLineEdit = new QLineEdit();
76  mLineEdit->setReadOnly( true );
77  editLayout->addWidget( mLineEdit );
78 
79  // open form button
80  mOpenFormButton = new QToolButton();
81  mOpenFormButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPropertyItem.svg" ) ) );
82  mOpenFormButton->setText( tr( "Open related feature form" ) );
83  editLayout->addWidget( mOpenFormButton );
84 
85  mAddEntryButton = new QToolButton();
86  mAddEntryButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAdd.svg" ) ) );
87  mAddEntryButton->setText( tr( "Add new entry" ) );
88  editLayout->addWidget( mAddEntryButton );
89 
90  // highlight button
91  mHighlightFeatureButton = new QToolButton( this );
92  mHighlightFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
93  mHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHighlightFeature.svg" ) ), tr( "Highlight feature" ), this );
94  mScaleHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleHighlightFeature.svg" ) ), tr( "Scale and highlight feature" ), this );
95  mPanHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPanHighlightFeature.svg" ) ), tr( "Pan and highlight feature" ), this );
96  mHighlightFeatureButton->addAction( mHighlightFeatureAction );
97  mHighlightFeatureButton->addAction( mScaleHighlightFeatureAction );
98  mHighlightFeatureButton->addAction( mPanHighlightFeatureAction );
99  mHighlightFeatureButton->setDefaultAction( mHighlightFeatureAction );
100  editLayout->addWidget( mHighlightFeatureButton );
101 
102  // map identification button
103  mMapIdentificationButton = new QToolButton( this );
104  mMapIdentificationButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMapIdentification.svg" ) ) );
105  mMapIdentificationButton->setText( tr( "Select on map" ) );
106  mMapIdentificationButton->setCheckable( true );
107  editLayout->addWidget( mMapIdentificationButton );
108 
109  // remove foreign key button
110  mRemoveFKButton = new QToolButton( this );
111  mRemoveFKButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemove.svg" ) ) );
112  mRemoveFKButton->setText( tr( "No selection" ) );
113  editLayout->addWidget( mRemoveFKButton );
114 
115  // add line to top layout
116  mTopLayout->addLayout( editLayout );
117 
118  // embed form
119  mAttributeEditorFrame = new QgsCollapsibleGroupBox( this );
120  mAttributeEditorLayout = new QVBoxLayout( mAttributeEditorFrame );
121  mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
122  mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
123  mTopLayout->addWidget( mAttributeEditorFrame );
124 
125  // invalid label
126  mInvalidLabel = new QLabel( tr( "The relation is not valid. Please make sure your relation definitions are OK." ) );
127  mInvalidLabel->setWordWrap( true );
128  QFont font = mInvalidLabel->font();
129  font.setItalic( true );
130  mInvalidLabel->setStyleSheet( QStringLiteral( "QLabel { color: red; } " ) );
131  mInvalidLabel->setFont( font );
132  mTopLayout->addWidget( mInvalidLabel );
133 
134  // default mode is combobox, no geometric relation and no embed form
135  mLineEdit->hide();
136  mMapIdentificationButton->hide();
137  mHighlightFeatureButton->hide();
138  mAttributeEditorFrame->hide();
139  mInvalidLabel->hide();
140 
141  // connect buttons
142  connect( mOpenFormButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::openForm );
143  connect( mHighlightFeatureButton, &QToolButton::triggered, this, &QgsRelationReferenceWidget::highlightActionTriggered );
144  connect( mMapIdentificationButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::mapIdentification );
145  connect( mRemoveFKButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::deleteForeignKey );
146  connect( mAddEntryButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::addEntry );
147  connect( mComboBox, &QComboBox::editTextChanged, this, &QgsRelationReferenceWidget::updateAddEntryButton );
148  connect( mComboBox, &QgsFeatureListComboBox::modelUpdated, this, &QgsRelationReferenceWidget::updateIndex );
149 }
150 
152 {
153  deleteHighlight();
154  unsetMapTool();
155  if ( mMapTool )
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  if ( mMapTool )
405  delete mMapTool;
406  mMapTool = new QgsMapToolIdentifyFeature( mCanvas );
407  mMapTool->setButton( mMapIdentificationButton );
408 }
409 
411 {
412  if ( display )
413  {
414  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
415  mTopLayout->setAlignment( Qt::AlignTop );
416  }
417 
418  mAttributeEditorFrame->setVisible( display );
419  mEmbedForm = display;
420 }
421 
423 {
424  mChooserContainer->setHidden( readOnly );
425  mLineEdit->setVisible( readOnly );
426  mRemoveFKButton->setVisible( mAllowNull && readOnly );
427  mReadOnlySelector = readOnly;
428 }
429 
431 {
432  mHighlightFeatureButton->setVisible( allowMapIdentification );
433  mMapIdentificationButton->setVisible( allowMapIdentification );
434  mAllowMapIdentification = allowMapIdentification;
435 }
436 
438 {
439  mOrderByValue = orderByValue;
440 }
441 
442 void QgsRelationReferenceWidget::setFilterFields( const QStringList &filterFields )
443 {
444  mFilterFields = filterFields;
445 }
446 
448 {
449  mOpenFormButton->setVisible( openFormButtonVisible );
450  mOpenFormButtonVisible = openFormButtonVisible;
451 }
452 
454 {
455  mChainFilters = chainFilters;
456 }
457 
459 {
460  Q_UNUSED( e )
461 
462  mShown = true;
463 
464  init();
465 }
466 
468 {
469  if ( !mReadOnlySelector && mReferencedLayer )
470  {
471  QApplication::setOverrideCursor( Qt::WaitCursor );
472 
473  QSet<QString> requestedAttrs;
474 
475  if ( !mFilterFields.isEmpty() )
476  {
477  for ( const QString &fieldName : qgis::as_const( mFilterFields ) )
478  {
479  int idx = mReferencedLayer->fields().lookupField( fieldName );
480 
481  if ( idx == -1 )
482  continue;
483 
484  QComboBox *cb = new QComboBox();
485  cb->setProperty( "Field", fieldName );
486  cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
487  mFilterComboBoxes << cb;
488  QVariantList uniqueValues = mReferencedLayer->uniqueValues( idx ).toList();
489  cb->addItem( mReferencedLayer->attributeDisplayName( idx ) );
490  QVariant nullValue = QgsApplication::nullRepresentation();
491  cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->fields().at( idx ).type() ) );
492 
493  std::sort( uniqueValues.begin(), uniqueValues.end(), qgsVariantLessThan );
494  Q_FOREACH ( const QVariant &v, uniqueValues )
495  {
496  cb->addItem( v.toString(), v );
497  }
498 
499  connect( cb, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::filterChanged );
500 
501  // Request this attribute for caching
502  requestedAttrs << fieldName;
503 
504  mFilterLayout->addWidget( cb );
505  }
506 
507  if ( mChainFilters )
508  {
509  QVariant nullValue = QgsApplication::nullRepresentation();
510 
511  QgsFeature ft;
512  QgsFeatureIterator fit = mReferencedLayer->getFeatures();
513  while ( fit.nextFeature( ft ) )
514  {
515  const int count = std::min( mFilterComboBoxes.count(), mFilterFields.count() );
516  for ( int i = 0; i < count - 1; i++ )
517  {
518  QVariant cv = ft.attribute( mFilterFields.at( i ) );
519  QVariant nv = ft.attribute( mFilterFields.at( i + 1 ) );
520  QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
521  QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
522  mFilterCache[mFilterFields[i]][cf] << nf;
523  }
524  }
525 
526  if ( !mFilterComboBoxes.isEmpty() )
527  {
528  QComboBox *cb = mFilterComboBoxes.first();
529  cb->setCurrentIndex( 0 );
530  disableChainedComboBoxes( cb );
531  }
532  }
533  }
534  else
535  {
536  mFilterContainer->hide();
537  }
538 
539  mComboBox->setSourceLayer( mReferencedLayer );
540  mComboBox->setDisplayExpression( mReferencedLayer->displayExpression() );
541  mComboBox->setAllowNull( mAllowNull );
542  mComboBox->setIdentifierField( mReferencedField );
543 
544  QVariant nullValue = QgsApplication::nullRepresentation();
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  // Only connect after iterating, to have only one iterator on the referenced table at once
557  connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::comboReferenceChanged );
558  updateAttributeEditorFrame( mFeature );
559  QApplication::restoreOverrideCursor();
560  }
561 }
562 
563 void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
564 {
565  if ( action == mHighlightFeatureAction )
566  {
567  highlightFeature();
568  }
569  else if ( action == mScaleHighlightFeatureAction )
570  {
571  highlightFeature( QgsFeature(), Scale );
572  }
573  else if ( action == mPanHighlightFeatureAction )
574  {
575  highlightFeature( QgsFeature(), Pan );
576  }
577 }
578 
580 {
581  QgsFeature feat = referencedFeature();
582 
583  if ( !feat.isValid() )
584  return;
585 
587  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
588  attributeDialog.exec();
589 }
590 
591 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
592 {
593  if ( !mCanvas )
594  return;
595 
596  if ( !f.isValid() )
597  {
598  f = referencedFeature();
599  if ( !f.isValid() )
600  return;
601  }
602 
603  if ( !f.hasGeometry() )
604  {
605  return;
606  }
607 
608  QgsGeometry geom = f.geometry();
609 
610  // scale or pan
611  if ( canvasExtent == Scale )
612  {
613  QgsRectangle featBBox = geom.boundingBox();
614  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
615  QgsRectangle extent = mCanvas->extent();
616  if ( !extent.contains( featBBox ) )
617  {
618  extent.combineExtentWith( featBBox );
619  extent.scale( 1.1 );
620  mCanvas->setExtent( extent );
621  mCanvas->refresh();
622  }
623  }
624  else if ( canvasExtent == Pan )
625  {
626  QgsGeometry centroid = geom.centroid();
627  QgsPointXY center = centroid.asPoint();
628  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
629  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
630  }
631 
632  // highlight
633  deleteHighlight();
634  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
635  QgsSettings settings;
636  QColor color = QColor( settings.value( QStringLiteral( "Map/highlight/color" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
637  int alpha = settings.value( QStringLiteral( "Map/highlight/colorAlpha" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
638  double buffer = settings.value( QStringLiteral( "Map/highlight/buffer" ), Qgis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
639  double minWidth = settings.value( QStringLiteral( "Map/highlight/minWidth" ), Qgis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
640 
641  mHighlight->setColor( color ); // sets also fill with default alpha
642  color.setAlpha( alpha );
643  mHighlight->setFillColor( color ); // sets fill with alpha
644  mHighlight->setBuffer( buffer );
645  mHighlight->setMinWidth( minWidth );
646  mHighlight->show();
647 
648  QTimer *timer = new QTimer( this );
649  timer->setSingleShot( true );
650  connect( timer, &QTimer::timeout, this, &QgsRelationReferenceWidget::deleteHighlight );
651  timer->start( 3000 );
652 }
653 
654 void QgsRelationReferenceWidget::deleteHighlight()
655 {
656  if ( mHighlight )
657  {
658  mHighlight->hide();
659  delete mHighlight;
660  }
661  mHighlight = nullptr;
662 }
663 
665 {
666  if ( !mAllowMapIdentification || !mReferencedLayer )
667  return;
668 
669  const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
670  if ( !tools )
671  return;
672  if ( !mCanvas )
673  return;
674 
675  mMapTool->setLayer( mReferencedLayer );
676  mCanvas->setMapTool( mMapTool );
677 
678  mWindowWidget = window();
679 
680  mCanvas->window()->raise();
681  mCanvas->activateWindow();
682  mCanvas->setFocus();
683 
684  connect( mMapTool, static_cast<void ( QgsMapToolIdentifyFeature::* )( const QgsFeature & )>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
685  connect( mMapTool, &QgsMapTool::deactivated, this, &QgsRelationReferenceWidget::mapToolDeactivated );
686 
687  if ( mMessageBar )
688  {
689  QString title = tr( "Relation %1 for %2." ).arg( mRelationName, mReferencingLayer->name() );
690  QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
691  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
692  mMessageBar->pushItem( mMessageBarItem );
693  }
694 }
695 
696 void QgsRelationReferenceWidget::comboReferenceChanged( int index )
697 {
698  Q_UNUSED( index )
699  mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( mFeature );
700  highlightFeature( mFeature );
701  updateAttributeEditorFrame( mFeature );
702 
703  emitForeignKeyChanged( mComboBox->identifierValue() );
704 }
705 
706 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature &feature )
707 {
708  mOpenFormButton->setEnabled( feature.isValid() );
709  // Check if we're running with an embedded frame we need to update
710  if ( mAttributeEditorFrame && mReferencedAttributeForm )
711  {
712  mReferencedAttributeForm->setFeature( feature );
713  }
714 }
715 
717 {
718  return mAllowAddFeatures;
719 }
720 
722 {
723  mAllowAddFeatures = allowAddFeatures;
724  updateAddEntryButton();
725 }
726 
727 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature &feature )
728 {
729  if ( mReadOnlySelector )
730  {
731  QgsExpression expr( mReferencedLayer->displayExpression() );
733  context.setFeature( feature );
734  QString title = expr.evaluate( &context ).toString();
735  if ( expr.hasEvalError() )
736  {
737  title = feature.attribute( mReferencedFieldIdx ).toString();
738  }
739  mLineEdit->setText( title );
740  mForeignKey = feature.attribute( mReferencedFieldIdx );
741  mFeature = feature;
742  }
743  else
744  {
745  mComboBox->setCurrentIndex( mComboBox->findData( feature.id(), QgsAttributeTableModel::FeatureIdRole ) );
746  mFeature = feature;
747  }
748 
749  mRemoveFKButton->setEnabled( mIsEditable );
750  highlightFeature( feature );
751  updateAttributeEditorFrame( feature );
752  emit foreignKeyChanged( foreignKey() );
753 
754  unsetMapTool();
755 }
756 
757 void QgsRelationReferenceWidget::unsetMapTool()
758 {
759  // deactivate map tool if activated
760  if ( mCanvas && mMapTool )
761  {
762  /* this will call mapToolDeactivated */
763  mCanvas->unsetMapTool( mMapTool );
764  }
765 }
766 
767 void QgsRelationReferenceWidget::mapToolDeactivated()
768 {
769  if ( mWindowWidget )
770  {
771  mWindowWidget->raise();
772  mWindowWidget->activateWindow();
773  }
774 
775  if ( mMessageBar && mMessageBarItem )
776  {
777  mMessageBar->popWidget( mMessageBarItem );
778  }
779  mMessageBarItem = nullptr;
780 }
781 
782 void QgsRelationReferenceWidget::filterChanged()
783 {
784  QVariant nullValue = QgsApplication::nullRepresentation();
785 
786  QMap<QString, QString> filters;
787  QgsAttributeList attrs;
788 
789  QComboBox *scb = qobject_cast<QComboBox *>( sender() );
790 
791  Q_ASSERT( scb );
792 
793  QgsFeature f;
794  QgsFeatureIds featureIds;
795  QString filterExpression;
796 
797  // comboboxes have to be disabled before building filters
798  if ( mChainFilters )
799  disableChainedComboBoxes( scb );
800 
801  // build filters
802  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
803  {
804  if ( cb->currentIndex() != 0 )
805  {
806  const QString fieldName = cb->property( "Field" ).toString();
807 
808  if ( cb->currentText() == nullValue.toString() )
809  {
810  filters[fieldName] = QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
811  }
812  else
813  {
814  filters[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
815  }
816  attrs << mReferencedLayer->fields().lookupField( fieldName );
817  }
818  }
819 
820  if ( mChainFilters )
821  {
822  QComboBox *ccb = nullptr;
823  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
824  {
825  if ( !ccb )
826  {
827  if ( cb == scb )
828  ccb = cb;
829 
830  continue;
831  }
832 
833  if ( ccb->currentIndex() != 0 )
834  {
835  const QString fieldName = cb->property( "Field" ).toString();
836 
837  cb->blockSignals( true );
838  cb->clear();
839  cb->addItem( cb->property( "FieldAlias" ).toString() );
840 
841  // ccb = scb
842  // cb = scb + 1
843  QStringList texts;
844  Q_FOREACH ( const QString &txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
845  {
846  QMap<QString, QString> filtersAttrs = filters;
847  filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt );
848  QString expression = filtersAttrs.values().join( QStringLiteral( " AND " ) );
849 
850  QgsAttributeList subset = attrs;
851  subset << mReferencedLayer->fields().lookupField( fieldName );
852 
853  QgsFeatureIterator it( mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
854 
855  bool found = false;
856  while ( it.nextFeature( f ) )
857  {
858  if ( !featureIds.contains( f.id() ) )
859  featureIds << f.id();
860 
861  found = true;
862  }
863 
864  // item is only provided if at least 1 feature exists
865  if ( found )
866  texts << txt;
867  }
868 
869  texts.sort();
870  cb->addItems( texts );
871 
872  cb->setEnabled( true );
873  cb->blockSignals( false );
874 
875  ccb = cb;
876  }
877  }
878  }
879  filterExpression = filters.values().join( QStringLiteral( " AND " ) );
880  mComboBox->setFilterExpression( filterExpression );
881 }
882 
883 void QgsRelationReferenceWidget::addEntry()
884 {
885  QgsFeature f( mReferencedLayer->fields() );
886  QgsAttributeMap attributes;
887 
888  // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
889  if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
890  {
891  int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
892 
893  if ( fieldIdx != -1 )
894  {
895  attributes.insert( fieldIdx, mComboBox->currentText() );
896  }
897  }
898 
899  if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, QgsGeometry(), &f ) )
900  {
901  mComboBox->setIdentifierValue( f.attribute( mReferencingFieldIdx ) );
902  mAddEntryButton->setEnabled( false );
903  }
904 }
905 
906 void QgsRelationReferenceWidget::updateAddEntryButton()
907 {
908  mAddEntryButton->setVisible( mAllowAddFeatures );
909  mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
910 }
911 
912 void QgsRelationReferenceWidget::disableChainedComboBoxes( const QComboBox *scb )
913 {
914  QComboBox *ccb = nullptr;
915  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
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
Look up field&#39;s index from the field name.
Definition: qgsfields.cpp:299
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:176
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:45
QgsFeatureId id
Definition: qgsfeature.h:71
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:335
This offers a combobox with autocompleter that allows selecting features from a layer.
A rectangle specified with double values.
Definition: qgsrectangle.h:40
QgsFeatureRequest currentFeatureRequest() const
Shorthand for getting a feature request to query the currently selected feature.
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.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeature.h:544
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:234
void setOpenFormButtonVisible(bool openFormButtonVisible)
QgsVectorLayer referencingLayer
Definition: qgsrelation.h:43
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:121
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:104
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const override
Calculates a list of unique values contained within an attribute in the layer.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:62
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:190
bool isEditable() const override
Returns true if the provider is in editing mode.
int count() const
Returns number of items.
Definition: qgsfields.cpp:115
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:74
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:146
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:132
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:145
Get the feature id of the feature in this row.
void setBuffer(double buffer)
Set line / stroke buffer in millimeters.
Definition: qgshighlight.h:96
void setOrderByValue(bool orderByValue)
Sets if the widget will order the combobox entries by value.
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 override
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:39
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:44
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer&#39;s project and layer.
QgsGeometry geometry() const
Returns the geometry associated with this feature.
Definition: qgsfeature.cpp:101
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...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override
Query the layer for features specified in request.
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:352
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:181
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:224
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:46
QgsPointXY asPoint() const
Returns contents of the geometry as a point if wkbType is WKBPoint, otherwise returns [0...
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:131
QString name
Definition: qgsmaplayer.h:65
QList< int > QgsAttributeList
Definition: qgsfield.h:27
bool nextFeature(QgsFeature &f)
A vector of attributes.
Definition: qgsattributes.h:58
static const double DEFAULT_HIGHLIGHT_BUFFER_MM
Default highlight buffer in mm.
Definition: qgis.h:126
void setDisplayExpression(const QString &displayExpression)
The display expression will be used to display features as well as the the value to match the typed t...
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:255
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:55
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:103