QGIS API Documentation  2.99.0-Master (585a4d3)
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 }
149 
151 {
152  deleteHighlight();
153  unsetMapTool();
154  if ( mMapTool )
155  delete mMapTool;
156 }
157 
158 void QgsRelationReferenceWidget::setRelation( const QgsRelation &relation, bool allowNullValue )
159 {
160  mAllowNull = allowNullValue;
161  mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
162 
163  if ( relation.isValid() )
164  {
165  mInvalidLabel->hide();
166 
167  mRelation = relation;
168  mReferencingLayer = relation.referencingLayer();
169  mRelationName = relation.name();
170  mReferencedLayer = relation.referencedLayer();
171  mReferencedField = relation.fieldPairs().at( 0 ).second;
172  if ( mComboBox )
173  mComboBox->setIdentifierField( mReferencedField );
174 
175  mReferencedFieldIdx = mReferencedLayer->fields().lookupField( relation.fieldPairs().at( 0 ).second );
176  mReferencingFieldIdx = mReferencingLayer->fields().lookupField( relation.fieldPairs().at( 0 ).first );
177  mAttributeEditorFrame->setObjectName( QStringLiteral( "referencing/" ) + relation.name() );
178 
179 
180  if ( mEmbedForm )
181  {
183  mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
184  mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
185  mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
186  }
187 
188  connect( mReferencedLayer, &QgsVectorLayer::editingStarted, this, &QgsRelationReferenceWidget::updateAddEntryButton );
189  connect( mReferencedLayer, &QgsVectorLayer::editingStopped, this, &QgsRelationReferenceWidget::updateAddEntryButton );
190  updateAddEntryButton();
191  }
192  else
193  {
194  mInvalidLabel->show();
195  }
196 
197  if ( mShown && isVisible() )
198  {
199  init();
200  }
201 }
202 
204 {
205  if ( !editable )
206  unsetMapTool();
207 
208  mFilterContainer->setEnabled( editable );
209  mComboBox->setEnabled( editable );
210  mComboBox->setEditable( true );
211  mMapIdentificationButton->setEnabled( editable );
212  mRemoveFKButton->setEnabled( editable );
213  mIsEditable = editable;
214 }
215 
216 void QgsRelationReferenceWidget::setForeignKey( const QVariant &value )
217 {
218  if ( !value.isValid() )
219  {
220  return;
221  }
222  if ( value.isNull() )
223  {
225  return;
226  }
227 
228  if ( !mReferencedLayer )
229  return;
230 
231  if ( mReadOnlySelector )
232  {
233  // Attributes from the referencing layer
234  QgsAttributes attrs = QgsAttributes( mReferencingLayer->fields().count() );
235  // Set the value on the foreign key field of the referencing record
236  attrs[ mReferencingLayer->fields().lookupField( mRelation.fieldPairs().at( 0 ).first )] = value;
237 
238  QgsFeatureRequest request = mRelation.getReferencedFeatureRequest( attrs );
239 
240  mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
241 
242  if ( !mFeature.isValid() )
243  {
244  return;
245  }
246 
247  mForeignKey = mFeature.attribute( mReferencedFieldIdx );
248 
249  QgsExpression expr( mReferencedLayer->displayExpression() );
251  context.setFeature( mFeature );
252  QString title = expr.evaluate( &context ).toString();
253  if ( expr.hasEvalError() )
254  {
255  title = mFeature.attribute( mReferencedFieldIdx ).toString();
256  }
257  mLineEdit->setText( title );
258  }
259  else
260  {
261  mComboBox->setIdentifierValue( value );
262 
263  if ( mChainFilters )
264  {
265  QVariant nullValue = QgsApplication::nullRepresentation();
266 
267  QgsFeatureRequest request = mComboBox->currentFeatureRequest();
268 
269  mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
270 
271  const int count = std::min( mFilterComboBoxes.size(), mFilterFields.size() );
272  for ( int i = 0; i < count; i++ )
273  {
274  QVariant v = mFeature.attribute( mFilterFields[i] );
275  QString f = v.isNull() ? nullValue.toString() : v.toString();
276  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
277  }
278  }
279  }
280 
281  mRemoveFKButton->setEnabled( mIsEditable );
282  highlightFeature( mFeature ); // TODO : make this async
283  updateAttributeEditorFrame( mFeature );
284  emit foreignKeyChanged( foreignKey() );
285 }
286 
288 {
289  // deactivate filter comboboxes
290  if ( mChainFilters && !mFilterComboBoxes.isEmpty() )
291  {
292  QComboBox *cb = mFilterComboBoxes.first();
293  cb->setCurrentIndex( 0 );
294  disableChainedComboBoxes( cb );
295  }
296 
297  if ( mReadOnlySelector )
298  {
299  const QString nullValue = QgsApplication::nullRepresentation();
300 
301  QString nullText;
302  if ( mAllowNull )
303  {
304  nullText = tr( "%1 (no selection)" ).arg( nullValue );
305  }
306  mLineEdit->setText( nullText );
307  mForeignKey = QVariant();
308  mFeature.setValid( false );
309  }
310  else
311  {
312  mComboBox->setIdentifierValue( QVariant() );
313 
314 
315  }
316  mRemoveFKButton->setEnabled( false );
317  updateAttributeEditorFrame( QgsFeature() );
318  emit foreignKeyChanged( QVariant( QVariant::Int ) );
319 }
320 
322 {
323  QgsFeature f;
324  if ( mReferencedLayer )
325  {
326  QgsFeatureRequest request;
327  if ( mReadOnlySelector )
328  {
329  request = QgsFeatureRequest().setFilterFid( mFeature.id() );
330  }
331  else
332  {
333  request = mComboBox->currentFeatureRequest();
334  }
335  mReferencedLayer->getFeatures( request ).nextFeature( f );
336  }
337  return f;
338 }
339 
341 {
342  if ( mReadOnlySelector )
343  {
344  whileBlocking( mLineEdit )->setText( QString() );
345  }
346  else
347  {
348  whileBlocking( mComboBox )->setIdentifierValue( QVariant() );
349  }
350  mRemoveFKButton->setEnabled( false );
351  updateAttributeEditorFrame( QgsFeature() );
352 }
353 
355 {
356  if ( mReadOnlySelector )
357  {
358  return mForeignKey;
359  }
360  else
361  {
362  return mComboBox->identifierValue();
363  }
364 }
365 
367 {
368  mEditorContext = context;
369  mCanvas = canvas;
370  mMessageBar = messageBar;
371 
372  if ( mMapTool )
373  delete mMapTool;
374  mMapTool = new QgsMapToolIdentifyFeature( mCanvas );
375  mMapTool->setButton( mMapIdentificationButton );
376 }
377 
379 {
380  if ( display )
381  {
382  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
383  mTopLayout->setAlignment( Qt::AlignTop );
384  }
385 
386  mAttributeEditorFrame->setVisible( display );
387  mEmbedForm = display;
388 }
389 
391 {
392  mChooserContainer->setHidden( readOnly );
393  mLineEdit->setVisible( readOnly );
394  mRemoveFKButton->setVisible( mAllowNull && readOnly );
395  mReadOnlySelector = readOnly;
396 }
397 
399 {
400  mHighlightFeatureButton->setVisible( allowMapIdentification );
401  mMapIdentificationButton->setVisible( allowMapIdentification );
402  mAllowMapIdentification = allowMapIdentification;
403 }
404 
406 {
407  mOrderByValue = orderByValue;
408 }
409 
410 void QgsRelationReferenceWidget::setFilterFields( const QStringList &filterFields )
411 {
412  mFilterFields = filterFields;
413 }
414 
416 {
417  mOpenFormButton->setVisible( openFormButtonVisible );
418  mOpenFormButtonVisible = openFormButtonVisible;
419 }
420 
422 {
423  mChainFilters = chainFilters;
424 }
425 
427 {
428  Q_UNUSED( e )
429 
430  mShown = true;
431 
432  init();
433 }
434 
436 {
437  if ( !mReadOnlySelector && mReferencedLayer )
438  {
439  QApplication::setOverrideCursor( Qt::WaitCursor );
440 
441  QSet<QString> requestedAttrs;
442 
443  if ( !mFilterFields.isEmpty() )
444  {
445  for ( const QString &fieldName : qgis::as_const( mFilterFields ) )
446  {
447  int idx = mReferencedLayer->fields().lookupField( fieldName );
448 
449  if ( idx == -1 )
450  continue;
451 
452  QComboBox *cb = new QComboBox();
453  cb->setProperty( "Field", fieldName );
454  cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
455  mFilterComboBoxes << cb;
456  QVariantList uniqueValues = mReferencedLayer->uniqueValues( idx ).toList();
457  cb->addItem( mReferencedLayer->attributeDisplayName( idx ) );
458  QVariant nullValue = QgsApplication::nullRepresentation();
459  cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->fields().at( idx ).type() ) );
460 
461  std::sort( uniqueValues.begin(), uniqueValues.end(), qgsVariantLessThan );
462  Q_FOREACH ( const QVariant &v, uniqueValues )
463  {
464  cb->addItem( v.toString(), v );
465  }
466 
467  connect( cb, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::filterChanged );
468 
469  // Request this attribute for caching
470  requestedAttrs << fieldName;
471 
472  mFilterLayout->addWidget( cb );
473  }
474 
475  if ( mChainFilters )
476  {
477  QVariant nullValue = QgsApplication::nullRepresentation();
478 
479  QgsFeature ft;
480  QgsFeatureIterator fit = mReferencedLayer->getFeatures();
481  while ( fit.nextFeature( ft ) )
482  {
483  const int count = std::min( mFilterComboBoxes.count(), mFilterFields.count() );
484  for ( int i = 0; i < count - 1; i++ )
485  {
486  QVariant cv = ft.attribute( mFilterFields.at( i ) );
487  QVariant nv = ft.attribute( mFilterFields.at( i + 1 ) );
488  QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
489  QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
490  mFilterCache[mFilterFields[i]][cf] << nf;
491  }
492  }
493 
494  if ( !mFilterComboBoxes.isEmpty() )
495  {
496  QComboBox *cb = mFilterComboBoxes.first();
497  cb->setCurrentIndex( 0 );
498  disableChainedComboBoxes( cb );
499  }
500  }
501  }
502  else
503  {
504  mFilterContainer->hide();
505  }
506 
507  mComboBox->setSourceLayer( mReferencedLayer );
508  mComboBox->setDisplayExpression( mReferencedLayer->displayExpression() );
509  mComboBox->setAllowNull( mAllowNull );
510  mComboBox->setIdentifierField( mReferencedField );
511 
512  QVariant nullValue = QgsApplication::nullRepresentation();
513 
514  if ( mChainFilters && mFeature.isValid() )
515  {
516  for ( int i = 0; i < mFilterFields.size(); i++ )
517  {
518  QVariant v = mFeature.attribute( mFilterFields[i] );
519  QString f = v.isNull() ? nullValue.toString() : v.toString();
520  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
521  }
522  }
523 
524  // Only connect after iterating, to have only one iterator on the referenced table at once
525  connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::comboReferenceChanged );
526  updateAttributeEditorFrame( mFeature );
527  QApplication::restoreOverrideCursor();
528  }
529 }
530 
531 void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
532 {
533  if ( action == mHighlightFeatureAction )
534  {
535  highlightFeature();
536  }
537  else if ( action == mScaleHighlightFeatureAction )
538  {
539  highlightFeature( QgsFeature(), Scale );
540  }
541  else if ( action == mPanHighlightFeatureAction )
542  {
543  highlightFeature( QgsFeature(), Pan );
544  }
545 }
546 
548 {
549  QgsFeature feat = referencedFeature();
550 
551  if ( !feat.isValid() )
552  return;
553 
555  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
556  attributeDialog.exec();
557 }
558 
559 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
560 {
561  if ( !mCanvas )
562  return;
563 
564  if ( !f.isValid() )
565  {
566  f = referencedFeature();
567  if ( !f.isValid() )
568  return;
569  }
570 
571  if ( !f.hasGeometry() )
572  {
573  return;
574  }
575 
576  QgsGeometry geom = f.geometry();
577 
578  // scale or pan
579  if ( canvasExtent == Scale )
580  {
581  QgsRectangle featBBox = geom.boundingBox();
582  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
583  QgsRectangle extent = mCanvas->extent();
584  if ( !extent.contains( featBBox ) )
585  {
586  extent.combineExtentWith( featBBox );
587  extent.scale( 1.1 );
588  mCanvas->setExtent( extent );
589  mCanvas->refresh();
590  }
591  }
592  else if ( canvasExtent == Pan )
593  {
594  QgsGeometry centroid = geom.centroid();
595  QgsPointXY center = centroid.asPoint();
596  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
597  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
598  }
599 
600  // highlight
601  deleteHighlight();
602  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
603  QgsSettings settings;
604  QColor color = QColor( settings.value( QStringLiteral( "Map/highlight/color" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
605  int alpha = settings.value( QStringLiteral( "Map/highlight/colorAlpha" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
606  double buffer = settings.value( QStringLiteral( "Map/highlight/buffer" ), Qgis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
607  double minWidth = settings.value( QStringLiteral( "Map/highlight/minWidth" ), Qgis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
608 
609  mHighlight->setColor( color ); // sets also fill with default alpha
610  color.setAlpha( alpha );
611  mHighlight->setFillColor( color ); // sets fill with alpha
612  mHighlight->setBuffer( buffer );
613  mHighlight->setMinWidth( minWidth );
614  mHighlight->show();
615 
616  QTimer *timer = new QTimer( this );
617  timer->setSingleShot( true );
618  connect( timer, &QTimer::timeout, this, &QgsRelationReferenceWidget::deleteHighlight );
619  timer->start( 3000 );
620 }
621 
622 void QgsRelationReferenceWidget::deleteHighlight()
623 {
624  if ( mHighlight )
625  {
626  mHighlight->hide();
627  delete mHighlight;
628  }
629  mHighlight = nullptr;
630 }
631 
633 {
634  if ( !mAllowMapIdentification || !mReferencedLayer )
635  return;
636 
637  const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
638  if ( !tools )
639  return;
640  if ( !mCanvas )
641  return;
642 
643  mMapTool->setLayer( mReferencedLayer );
644  mCanvas->setMapTool( mMapTool );
645 
646  mWindowWidget = window();
647 
648  mCanvas->window()->raise();
649  mCanvas->activateWindow();
650  mCanvas->setFocus();
651 
652  connect( mMapTool, static_cast<void ( QgsMapToolIdentifyFeature::* )( const QgsFeature & )>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
653  connect( mMapTool, &QgsMapTool::deactivated, this, &QgsRelationReferenceWidget::mapToolDeactivated );
654 
655  if ( mMessageBar )
656  {
657  QString title = tr( "Relation %1 for %2." ).arg( mRelationName, mReferencingLayer->name() );
658  QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
659  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
660  mMessageBar->pushItem( mMessageBarItem );
661  }
662 }
663 
664 void QgsRelationReferenceWidget::comboReferenceChanged( int index )
665 {
666  Q_UNUSED( index )
667  mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( mFeature );
668  highlightFeature( mFeature );
669  updateAttributeEditorFrame( mFeature );
670  emit foreignKeyChanged( mFeature.attribute( mReferencedFieldIdx ) );
671 }
672 
673 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature &feature )
674 {
675  mOpenFormButton->setEnabled( feature.isValid() );
676  // Check if we're running with an embedded frame we need to update
677  if ( mAttributeEditorFrame && mReferencedAttributeForm )
678  {
679  mReferencedAttributeForm->setFeature( feature );
680  }
681 }
682 
684 {
685  return mAllowAddFeatures;
686 }
687 
689 {
690  mAllowAddFeatures = allowAddFeatures;
691  updateAddEntryButton();
692 }
693 
694 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature &feature )
695 {
696  if ( mReadOnlySelector )
697  {
698  QgsExpression expr( mReferencedLayer->displayExpression() );
700  context.setFeature( feature );
701  QString title = expr.evaluate( &context ).toString();
702  if ( expr.hasEvalError() )
703  {
704  title = feature.attribute( mReferencedFieldIdx ).toString();
705  }
706  mLineEdit->setText( title );
707  mForeignKey = feature.attribute( mReferencedFieldIdx );
708  mFeature = feature;
709  }
710  else
711  {
712  mComboBox->setCurrentIndex( mComboBox->findData( feature.id(), QgsAttributeTableModel::FeatureIdRole ) );
713  mFeature = feature;
714  }
715 
716  mRemoveFKButton->setEnabled( mIsEditable );
717  highlightFeature( feature );
718  updateAttributeEditorFrame( feature );
719  emit foreignKeyChanged( foreignKey() );
720 
721  unsetMapTool();
722 }
723 
724 void QgsRelationReferenceWidget::unsetMapTool()
725 {
726  // deactivate map tool if activated
727  if ( mCanvas && mMapTool )
728  {
729  /* this will call mapToolDeactivated */
730  mCanvas->unsetMapTool( mMapTool );
731  }
732 }
733 
734 void QgsRelationReferenceWidget::mapToolDeactivated()
735 {
736  if ( mWindowWidget )
737  {
738  mWindowWidget->raise();
739  mWindowWidget->activateWindow();
740  }
741 
742  if ( mMessageBar && mMessageBarItem )
743  {
744  mMessageBar->popWidget( mMessageBarItem );
745  }
746  mMessageBarItem = nullptr;
747 }
748 
749 void QgsRelationReferenceWidget::filterChanged()
750 {
751  QVariant nullValue = QgsApplication::nullRepresentation();
752 
753  QMap<QString, QString> filters;
754  QgsAttributeList attrs;
755 
756  QComboBox *scb = qobject_cast<QComboBox *>( sender() );
757 
758  Q_ASSERT( scb );
759 
760  QgsFeature f;
761  QgsFeatureIds featureIds;
762  QString filterExpression;
763 
764  // comboboxes have to be disabled before building filters
765  if ( mChainFilters )
766  disableChainedComboBoxes( scb );
767 
768  // build filters
769  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
770  {
771  if ( cb->currentIndex() != 0 )
772  {
773  const QString fieldName = cb->property( "Field" ).toString();
774 
775  if ( cb->currentText() == nullValue.toString() )
776  {
777  filters[fieldName] = QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
778  }
779  else
780  {
781  filters[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
782  }
783  attrs << mReferencedLayer->fields().lookupField( fieldName );
784  }
785  }
786 
787  if ( mChainFilters )
788  {
789  QComboBox *ccb = nullptr;
790  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
791  {
792  if ( !ccb )
793  {
794  if ( cb == scb )
795  ccb = cb;
796 
797  continue;
798  }
799 
800  if ( ccb->currentIndex() != 0 )
801  {
802  const QString fieldName = cb->property( "Field" ).toString();
803 
804  cb->blockSignals( true );
805  cb->clear();
806  cb->addItem( cb->property( "FieldAlias" ).toString() );
807 
808  // ccb = scb
809  // cb = scb + 1
810  QStringList texts;
811  Q_FOREACH ( const QString &txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
812  {
813  QMap<QString, QString> filtersAttrs = filters;
814  filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt );
815  QString expression = filtersAttrs.values().join( QStringLiteral( " AND " ) );
816 
817  QgsAttributeList subset = attrs;
818  subset << mReferencedLayer->fields().lookupField( fieldName );
819 
820  QgsFeatureIterator it( mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
821 
822  bool found = false;
823  while ( it.nextFeature( f ) )
824  {
825  if ( !featureIds.contains( f.id() ) )
826  featureIds << f.id();
827 
828  found = true;
829  }
830 
831  // item is only provided if at least 1 feature exists
832  if ( found )
833  texts << txt;
834  }
835 
836  texts.sort();
837  cb->addItems( texts );
838 
839  cb->setEnabled( true );
840  cb->blockSignals( false );
841 
842  ccb = cb;
843  }
844  }
845  }
846  filterExpression = filters.values().join( QStringLiteral( " AND " ) );
847  mComboBox->setFilterExpression( filterExpression );
848 }
849 
850 void QgsRelationReferenceWidget::addEntry()
851 {
852  QgsFeature f( mReferencedLayer->fields() );
853  QgsAttributeMap attributes;
854 
855  // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
856  if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
857  {
858  int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
859 
860  if ( fieldIdx != -1 )
861  {
862  attributes.insert( fieldIdx, mComboBox->currentText() );
863  }
864  }
865 
866  if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, QgsGeometry(), &f ) )
867  {
868  mComboBox->setIdentifierValue( f.attribute( mReferencingFieldIdx ) );
869  mAddEntryButton->setEnabled( false );
870  }
871 }
872 
873 void QgsRelationReferenceWidget::updateAddEntryButton()
874 {
875  mAddEntryButton->setVisible( mAllowAddFeatures );
876  mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
877 }
878 
879 void QgsRelationReferenceWidget::disableChainedComboBoxes( const QComboBox *scb )
880 {
881  QComboBox *ccb = nullptr;
882  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
883  {
884  if ( !ccb )
885  {
886  if ( cb == scb )
887  {
888  ccb = cb;
889  }
890 
891  continue;
892  }
893 
894  cb->setCurrentIndex( 0 );
895  if ( ccb->currentIndex() == 0 )
896  {
897  cb->setEnabled( false );
898  }
899 
900  ccb = cb;
901  }
902 }
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
void setIdentifierValue(const QVariant &identifierValue)
The identifier value of the currently selected feature.
const QgsVectorLayerTools * vectorLayerTools() const
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
Return true when rectangle contains other rectangle.
This offers a combobox with autocompleter that allows selecting features from a layer.
A rectangle specified with double values.
Definition: qgsrectangle.h:39
QgsFeatureRequest currentFeatureRequest() const
Shorthand for getting a feature request to query the currently selected feature.
void setLayer(QgsVectorLayer *vl)
change the layer used by the map tool to identify
QgsFeature referencedFeature() const
return the related feature (from the referenced layer) if no feature is related, it returns an invali...
bool chainFilters() const
Determines if the filters are chained.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:57
A groupbox that collapses/expands when toggled and can save its collapsed and checked states...
void setFilterFields(const QStringList &filterFields)
Set the fields for which filter comboboxes will be created.
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.
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:111
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
Return 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
Get 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)
Set if the widget will order the combobox entries by value.
QgsFeatureRequest & setFilterFid(QgsFeatureId fid)
Set 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.
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
Get access to properties used for map rendering.
void combineExtentWith(const QgsRectangle &rect)
Expand the rectangle so that covers both the original rectangle and the given rectangle.
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 &)
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=NoSection) const
Returns the value for setting key.
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)
Set 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:60
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...
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