QGIS API Documentation  3.27.0-Master (0e23467727)
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"
42 #include "qgsfeatureiterator.h"
43 #include "qgsfeaturelistcombobox.h"
45 #include "qgsfeaturefiltermodel.h"
46 #include "qgsidentifymenu.h"
47 #include "qgsvectorlayerutils.h"
48 
49 
50 bool qVariantListIsNull( const QVariantList &list )
51 {
52  if ( list.isEmpty() )
53  return true;
54 
55  for ( int i = 0; i < list.size(); ++i )
56  {
57  if ( !list.at( i ).isNull() )
58  return false;
59  }
60  return true;
61 }
62 
63 
65  : QWidget( parent )
66 {
67  mTopLayout = new QVBoxLayout( this );
68  mTopLayout->setContentsMargins( 0, 0, 0, 0 );
69 
70  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::Fixed );
71 
72  setLayout( mTopLayout );
73 
74  QHBoxLayout *editLayout = new QHBoxLayout();
75  editLayout->setContentsMargins( 0, 0, 0, 0 );
76  editLayout->setSpacing( 2 );
77 
78  // Prepare the container and layout for the filter comboboxes
79  mChooserContainer = new QWidget;
80  editLayout->addWidget( mChooserContainer );
81  QHBoxLayout *chooserLayout = new QHBoxLayout;
82  chooserLayout->setContentsMargins( 0, 0, 0, 0 );
83  mFilterLayout = new QHBoxLayout;
84  mFilterLayout->setContentsMargins( 0, 0, 0, 0 );
85  mFilterContainer = new QWidget;
86  mFilterContainer->setLayout( mFilterLayout );
87  mChooserContainer->setLayout( chooserLayout );
88  chooserLayout->addWidget( mFilterContainer );
89 
90  mComboBox = new QgsFeatureListComboBox();
91  mChooserContainer->layout()->addWidget( mComboBox );
92 
93  // open form button
94  mOpenFormButton = new QToolButton();
95  mOpenFormButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPropertyItem.svg" ) ) );
96  mOpenFormButton->setText( tr( "Open Related Feature Form" ) );
97  editLayout->addWidget( mOpenFormButton );
98 
99  mAddEntryButton = new QToolButton();
100  mAddEntryButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAdd.svg" ) ) );
101  mAddEntryButton->setText( tr( "Add New Entry" ) );
102  editLayout->addWidget( mAddEntryButton );
103 
104  // highlight button
105  mHighlightFeatureButton = new QToolButton( this );
106  mHighlightFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
107  mHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHighlightFeature.svg" ) ), tr( "Highlight feature" ), this );
108  mScaleHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleHighlightFeature.svg" ) ), tr( "Scale and highlight feature" ), this );
109  mPanHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPanHighlightFeature.svg" ) ), tr( "Pan and highlight feature" ), this );
110  mHighlightFeatureButton->addAction( mHighlightFeatureAction );
111  mHighlightFeatureButton->addAction( mScaleHighlightFeatureAction );
112  mHighlightFeatureButton->addAction( mPanHighlightFeatureAction );
113  mHighlightFeatureButton->setDefaultAction( mHighlightFeatureAction );
114  editLayout->addWidget( mHighlightFeatureButton );
115 
116  // map identification button
117  mMapIdentificationButton = new QToolButton( this );
118  mMapIdentificationButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMapIdentification.svg" ) ) );
119  mMapIdentificationButton->setText( tr( "Select on Map" ) );
120  mMapIdentificationButton->setCheckable( true );
121  editLayout->addWidget( mMapIdentificationButton );
122 
123  // remove foreign key button
124  mRemoveFKButton = new QToolButton( this );
125  mRemoveFKButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemove.svg" ) ) );
126  mRemoveFKButton->setText( tr( "No Selection" ) );
127  editLayout->addWidget( mRemoveFKButton );
128 
129  // add line to top layout
130  mTopLayout->addLayout( editLayout );
131 
132  // embed form
133  mAttributeEditorFrame = new QgsCollapsibleGroupBox( this );
134  mAttributeEditorLayout = new QVBoxLayout( mAttributeEditorFrame );
135  mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
136  mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
137  mTopLayout->addWidget( mAttributeEditorFrame );
138 
139  // invalid label
140  mInvalidLabel = new QLabel( tr( "The relation is not valid. Please make sure your relation definitions are OK." ) );
141  mInvalidLabel->setWordWrap( true );
142  QFont font = mInvalidLabel->font();
143  font.setItalic( true );
144  mInvalidLabel->setStyleSheet( QStringLiteral( "QLabel { color: red; } " ) );
145  mInvalidLabel->setFont( font );
146  mTopLayout->addWidget( mInvalidLabel );
147 
148  // default mode is combobox, no geometric relation and no embed form
149  mMapIdentificationButton->hide();
150  mHighlightFeatureButton->hide();
151  mAttributeEditorFrame->hide();
152  mInvalidLabel->hide();
153  mAddEntryButton->hide();
154 
155  // connect buttons
156  connect( mOpenFormButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::openForm );
157  connect( mHighlightFeatureButton, &QToolButton::triggered, this, &QgsRelationReferenceWidget::highlightActionTriggered );
158  connect( mMapIdentificationButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::mapIdentification );
159  connect( mRemoveFKButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::deleteForeignKeys );
160  connect( mAddEntryButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::addEntry );
161  connect( mComboBox, &QComboBox::editTextChanged, this, &QgsRelationReferenceWidget::updateAddEntryButton );
162 }
163 
165 {
166  deleteHighlight();
167  unsetMapTool();
168 }
169 
170 void QgsRelationReferenceWidget::setRelation( const QgsRelation &relation, bool allowNullValue )
171 {
172  mAllowNull = allowNullValue;
173  mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
174 
175  if ( relation.isValid() )
176  {
177  mReferencedLayerId = relation.referencedLayerId();
178  mReferencedLayerName = relation.referencedLayer()->name();
180  mReferencedLayerProviderKey = relation.referencedLayer()->providerType();
181  mInvalidLabel->hide();
182 
183  mRelation = relation;
184  mReferencingLayer = relation.referencingLayer();
185  mReferencedLayer = relation.referencedLayer();
186 
187  const QList<QgsRelation::FieldPair> fieldPairs = relation.fieldPairs();
188  for ( const QgsRelation::FieldPair &fieldPair : fieldPairs )
189  {
190  mReferencedFields << fieldPair.referencedField();
191  }
192  if ( mComboBox )
193  {
194  mComboBox->setAllowNull( mAllowNull );
195  mComboBox->setSourceLayer( mReferencedLayer );
196  mComboBox->setIdentifierFields( mReferencedFields );
197  mComboBox->setFilterExpression( mFilterExpression );
198  }
199  mAttributeEditorFrame->setObjectName( QStringLiteral( "referencing/" ) + relation.name() );
200 
201  if ( mEmbedForm )
202  {
204  mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
205  mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
206  mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
207  }
208 
209  connect( mReferencedLayer, &QgsVectorLayer::editingStarted, this, &QgsRelationReferenceWidget::updateAddEntryButton );
210  connect( mReferencedLayer, &QgsVectorLayer::editingStopped, this, &QgsRelationReferenceWidget::updateAddEntryButton );
211  updateAddEntryButton();
212  }
213  else
214  {
215  mInvalidLabel->show();
216  }
217 
218  if ( mShown && isVisible() )
219  {
220  init();
221  }
222 }
223 
225 {
226  if ( !editable )
227  {
228  unsetMapTool();
229  }
230 
231  mFilterContainer->setEnabled( editable );
232  mComboBox->setEnabled( editable && !mReadOnlySelector );
233  mComboBox->setEditable( true );
234  mMapIdentificationButton->setEnabled( editable );
235  mRemoveFKButton->setEnabled( editable );
236  mIsEditable = editable;
237 }
238 
239 void QgsRelationReferenceWidget::setForeignKey( const QVariant &value )
240 {
241  setForeignKeys( QVariantList() << value );
242 }
243 
244 void QgsRelationReferenceWidget::setForeignKeys( const QVariantList &values )
245 {
246  if ( values.isEmpty() )
247  {
248  return;
249  }
250  if ( qVariantListIsNull( values ) )
251  {
253  return;
254  }
255 
256  if ( !mReferencedLayer )
257  return;
258 
259  mComboBox->setIdentifierValues( values );
260 
261  if ( mEmbedForm || mChainFilters )
262  {
263  QgsFeatureRequest request = mComboBox->currentFeatureRequest();
264  mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
265  }
266  if ( mChainFilters )
267  {
268  QVariant nullValue = QgsApplication::nullRepresentation();
269  const int count = std::min( mFilterComboBoxes.size(), mFilterFields.size() );
270  for ( int i = 0; i < count; i++ )
271  {
272  QVariant v = mFeature.attribute( mFilterFields[i] );
273  QString f = v.isNull() ? nullValue.toString() : v.toString();
274  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
275  }
276  }
277 
278  mRemoveFKButton->setEnabled( mIsEditable );
279  highlightFeature( mFeature ); // TODO : make this async
280  updateAttributeEditorFrame( mFeature );
281 
282  emitForeignKeysChanged( foreignKeys() );
283 }
284 
286 {
287  // deactivate filter comboboxes
288  if ( mChainFilters && !mFilterComboBoxes.isEmpty() )
289  {
290  QComboBox *cb = mFilterComboBoxes.first();
291  cb->setCurrentIndex( 0 );
292  disableChainedComboBoxes( cb );
293  }
294 
295  mComboBox->setIdentifierValuesToNull();
296  mRemoveFKButton->setEnabled( false );
297  updateAttributeEditorFrame( QgsFeature() );
298 
299  emitForeignKeysChanged( foreignKeys() );
300 }
301 
303 {
304  QgsFeature f;
305  if ( mReferencedLayer )
306  {
307  mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( f );
308  }
309  return f;
310 }
311 
313 {
314  whileBlocking( mComboBox )->setIdentifierValuesToNull();
315  mRemoveFKButton->setEnabled( false );
316  updateAttributeEditorFrame( QgsFeature() );
317 }
318 
320 {
321  QVariantList fkeys;
322  if ( fkeys.isEmpty() )
323  return QVariant( QVariant::Int );
324  else
325  return fkeys.at( 0 );
326 }
327 
329 {
330  return mComboBox->identifierValues();
331 }
332 
334 {
335  mEditorContext = context;
336  mCanvas = canvas;
337  mMessageBar = messageBar;
338 
339  mMapToolIdentify.reset( new QgsMapToolIdentifyFeature( mCanvas ) );
340  mMapToolIdentify->setButton( mMapIdentificationButton );
341 
342  if ( mEditorContext.cadDockWidget() )
343  {
344  mMapToolDigitize.reset( new QgsMapToolDigitizeFeature( mCanvas, mEditorContext.cadDockWidget() ) );
345  mMapToolDigitize->setButton( mAddEntryButton );
346  updateAddEntryButton();
347  }
348 }
349 
351 {
352  if ( display )
353  {
354  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
355  mTopLayout->setAlignment( Qt::AlignTop );
356  }
357 
358  mAttributeEditorFrame->setVisible( display );
359  mEmbedForm = display;
360 }
361 
363 {
364  mComboBox->setEnabled( !readOnly );
365  mRemoveFKButton->setVisible( mAllowNull && readOnly );
366  mReadOnlySelector = readOnly;
367 }
368 
369 void QgsRelationReferenceWidget::setAllowMapIdentification( bool allowMapIdentification )
370 {
371  mHighlightFeatureButton->setVisible( allowMapIdentification );
372  mMapIdentificationButton->setVisible( allowMapIdentification );
373  mAllowMapIdentification = allowMapIdentification;
374 }
375 
377 {
378  mOrderByValue = orderByValue;
379 }
380 
381 void QgsRelationReferenceWidget::setFilterFields( const QStringList &filterFields )
382 {
383  mFilterFields = filterFields;
384 }
385 
387 {
388  mOpenFormButton->setVisible( openFormButtonVisible );
389  mOpenFormButtonVisible = openFormButtonVisible;
390 }
391 
393 {
394  mChainFilters = chainFilters;
395 }
396 
397 void QgsRelationReferenceWidget::setFilterExpression( const QString &expression )
398 {
399  mFilterExpression = expression;
400 }
401 
403 {
404  Q_UNUSED( e )
405 
406  mShown = true;
407  if ( !mInitialized )
408  init();
409 }
410 
412 {
413  if ( mReferencedLayer )
414  {
415  QApplication::setOverrideCursor( Qt::WaitCursor );
416 
417  QSet<QString> requestedAttrs;
418 
419  if ( !mFilterFields.isEmpty() )
420  {
421  for ( const QString &fieldName : std::as_const( mFilterFields ) )
422  {
423  int idx = mReferencedLayer->fields().lookupField( fieldName );
424 
425  if ( idx == -1 )
426  continue;
427 
428  QComboBox *cb = new QComboBox();
429  cb->setProperty( "Field", fieldName );
430  cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
431  mFilterComboBoxes << cb;
432  QVariantList uniqueValues = qgis::setToList( mReferencedLayer->uniqueValues( idx ) );
433  cb->addItem( mReferencedLayer->attributeDisplayName( idx ) );
434  QVariant nullValue = QgsApplication::nullRepresentation();
435  cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->fields().at( idx ).type() ) );
436 
437  std::sort( uniqueValues.begin(), uniqueValues.end(), qgsVariantLessThan );
438  const auto constUniqueValues = uniqueValues;
439  for ( const QVariant &v : constUniqueValues )
440  {
441  cb->addItem( v.toString(), v );
442  }
443 
444  connect( cb, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::filterChanged );
445 
446  // Request this attribute for caching
447  requestedAttrs << fieldName;
448 
449  mFilterLayout->addWidget( cb );
450  }
451 
452  if ( mChainFilters )
453  {
454  QVariant nullValue = QgsApplication::nullRepresentation();
455 
456  QgsFeature ft;
457  QgsFeatureIterator fit = mFilterExpression.isEmpty()
458  ? mReferencedLayer->getFeatures()
459  : mReferencedLayer->getFeatures( mFilterExpression );
460  while ( fit.nextFeature( ft ) )
461  {
462  const int count = std::min( mFilterComboBoxes.count(), mFilterFields.count() );
463  for ( int i = 0; i < count - 1; i++ )
464  {
465  QVariant cv = ft.attribute( mFilterFields.at( i ) );
466  QVariant nv = ft.attribute( mFilterFields.at( i + 1 ) );
467  QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
468  QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
469  mFilterCache[mFilterFields[i]][cf] << nf;
470  }
471  }
472 
473  if ( !mFilterComboBoxes.isEmpty() )
474  {
475  QComboBox *cb = mFilterComboBoxes.first();
476  cb->setCurrentIndex( 0 );
477  disableChainedComboBoxes( cb );
478  }
479  }
480  }
481  else
482  {
483  mFilterContainer->hide();
484  }
485 
486  mComboBox->setSourceLayer( mReferencedLayer );
487  mComboBox->setDisplayExpression( mReferencedLayer->displayExpression() );
488  mComboBox->setAllowNull( mAllowNull );
489  mComboBox->setIdentifierFields( mReferencedFields );
490 
491  if ( ! mFilterExpression.isEmpty() )
492  mComboBox->setFilterExpression( mFilterExpression );
493 
494  QVariant nullValue = QgsApplication::nullRepresentation();
495 
496  if ( mChainFilters && mFeature.isValid() )
497  {
498  for ( int i = 0; i < mFilterFields.size(); i++ )
499  {
500  QVariant v = mFeature.attribute( mFilterFields[i] );
501  QString f = v.isNull() ? nullValue.toString() : v.toString();
502  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
503  }
504  }
505 
506  // Only connect after iterating, to have only one iterator on the referenced table at once
507  connect( mComboBox, &QgsFeatureListComboBox::currentFeatureChanged, this, &QgsRelationReferenceWidget::comboReferenceChanged );
508 
509  QApplication::restoreOverrideCursor();
510 
511  mInitialized = true;
512  }
513 }
514 
515 void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
516 {
517  if ( action == mHighlightFeatureAction )
518  {
519  highlightFeature();
520  }
521  else if ( action == mScaleHighlightFeatureAction )
522  {
523  highlightFeature( QgsFeature(), Scale );
524  }
525  else if ( action == mPanHighlightFeatureAction )
526  {
527  highlightFeature( QgsFeature(), Pan );
528  }
529 }
530 
532 {
533  QgsFeature feat = referencedFeature();
534 
535  if ( !feat.isValid() )
536  return;
537 
539  QgsAttributeDialog *attributeDialog = new QgsAttributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
540  attributeDialog->show();
541 }
542 
543 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
544 {
545  if ( !mCanvas )
546  return;
547 
548  if ( !f.isValid() )
549  {
550  f = referencedFeature();
551  if ( !f.isValid() )
552  return;
553  }
554 
555  if ( !f.hasGeometry() )
556  {
557  return;
558  }
559 
560  QgsGeometry geom = f.geometry();
561 
562  // scale or pan
563  if ( canvasExtent == Scale )
564  {
565  QgsRectangle featBBox = geom.boundingBox();
566  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
567  QgsRectangle extent = mCanvas->extent();
568  if ( !extent.contains( featBBox ) )
569  {
570  extent.combineExtentWith( featBBox );
571  extent.scale( 1.1 );
572  mCanvas->setExtent( extent, true );
573  mCanvas->refresh();
574  }
575  }
576  else if ( canvasExtent == Pan )
577  {
578  QgsGeometry centroid = geom.centroid();
579  QgsPointXY center = centroid.asPoint();
580  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
581  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
582  }
583 
584  // highlight
585  deleteHighlight();
586  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
587  QgsIdentifyMenu::styleHighlight( mHighlight );
588  mHighlight->show();
589 
590  QTimer *timer = new QTimer( this );
591  timer->setSingleShot( true );
592  connect( timer, &QTimer::timeout, this, &QgsRelationReferenceWidget::deleteHighlight );
593  timer->start( 3000 );
594 }
595 
596 void QgsRelationReferenceWidget::deleteHighlight()
597 {
598  if ( mHighlight )
599  {
600  mHighlight->hide();
601  delete mHighlight;
602  }
603  mHighlight = nullptr;
604 }
605 
607 {
608  if ( !mAllowMapIdentification || !mReferencedLayer )
609  return;
610 
611  const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
612  if ( !tools )
613  return;
614  if ( !mCanvas )
615  return;
616 
617  mMapToolIdentify->setLayer( mReferencedLayer );
618  setMapTool( mMapToolIdentify );
619 
620  connect( mMapToolIdentify, qOverload<const QgsFeature &>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
621 
622  if ( mMessageBar )
623  {
624  QString title = tr( "Relation %1 for %2." ).arg( mRelation.name(), mReferencingLayer->name() );
625  QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
626  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
627  mMessageBar->pushItem( mMessageBarItem );
628  }
629 }
630 
631 void QgsRelationReferenceWidget::comboReferenceChanged()
632 {
633  mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( mFeature );
634  highlightFeature( mFeature );
635  updateAttributeEditorFrame( mFeature );
636 
637  emitForeignKeysChanged( mComboBox->identifierValues() );
638 }
639 
640 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature &feature )
641 {
642  mOpenFormButton->setEnabled( feature.isValid() );
643  // Check if we're running with an embedded frame we need to update
644  if ( mAttributeEditorFrame && mReferencedAttributeForm )
645  {
646  mReferencedAttributeForm->setFeature( feature );
647  }
648 }
649 
651 {
652  return mAllowAddFeatures;
653 }
654 
656 {
657  mAllowAddFeatures = allowAddFeatures;
658  updateAddEntryButton();
659 }
660 
662 {
663  return mRelation;
664 }
665 
666 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature &feature )
667 {
668  mComboBox->setCurrentFeature( feature );
669  mFeature = feature;
670 
671  mRemoveFKButton->setEnabled( mIsEditable );
672  highlightFeature( feature );
673  updateAttributeEditorFrame( feature );
674  emitForeignKeysChanged( foreignKeys(), true );
675 
676  unsetMapTool();
677 }
678 
679 void QgsRelationReferenceWidget::setMapTool( QgsMapTool *mapTool )
680 {
681  mCurrentMapTool = mapTool;
682  mCanvas->setMapTool( mapTool );
683 
684  mWindowWidget = window();
685 
686  mCanvas->window()->raise();
687  mCanvas->activateWindow();
688  mCanvas->setFocus();
689  connect( mapTool, &QgsMapTool::deactivated, this, &QgsRelationReferenceWidget::mapToolDeactivated );
690 }
691 
692 void QgsRelationReferenceWidget::unsetMapTool()
693 {
694  // deactivate map tools if activated
695  if ( mCurrentMapTool )
696  {
697  /* this will call mapToolDeactivated */
698  mCanvas->unsetMapTool( mCurrentMapTool );
699 
700  if ( mCurrentMapTool == mMapToolDigitize )
701  {
702  disconnect( mCanvas, &QgsMapCanvas::keyPressed, this, &QgsRelationReferenceWidget::onKeyPressed );
703  disconnect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationReferenceWidget::entryAdded );
704  }
705  else
706  {
707  disconnect( mMapToolIdentify, qOverload<const QgsFeature &>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
708  }
709  }
710 }
711 
712 void QgsRelationReferenceWidget::onKeyPressed( QKeyEvent *e )
713 {
714  if ( e->key() == Qt::Key_Escape )
715  {
716  unsetMapTool();
717  }
718 }
719 
720 void QgsRelationReferenceWidget::mapToolDeactivated()
721 {
722  if ( mWindowWidget )
723  {
724  mWindowWidget->raise();
725  mWindowWidget->activateWindow();
726  }
727 
728  if ( mMessageBar && mMessageBarItem )
729  {
730  mMessageBar->popWidget( mMessageBarItem );
731  }
732  mMessageBarItem = nullptr;
733 }
734 
735 void QgsRelationReferenceWidget::filterChanged()
736 {
737  QVariant nullValue = QgsApplication::nullRepresentation();
738 
739  QMap<QString, QString> filters;
740  QgsAttributeList attrs;
741 
742  QComboBox *scb = qobject_cast<QComboBox *>( sender() );
743 
744  Q_ASSERT( scb );
745 
746  QgsFeature f;
747  QgsFeatureIds featureIds;
748  QString filterExpression = mFilterExpression;
749 
750  // wrap the expression with parentheses as it might contain `OR`
751  if ( !filterExpression.isEmpty() )
752  filterExpression = QStringLiteral( " ( %1 ) " ).arg( filterExpression );
753 
754  // comboboxes have to be disabled before building filters
755  if ( mChainFilters )
756  disableChainedComboBoxes( scb );
757 
758  // build filters
759  const auto constMFilterComboBoxes = mFilterComboBoxes;
760  for ( QComboBox *cb : constMFilterComboBoxes )
761  {
762  if ( cb->currentIndex() != 0 )
763  {
764  const QString fieldName = cb->property( "Field" ).toString();
765 
766  if ( cb->currentText() == nullValue.toString() )
767  {
768  filters[fieldName] = QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
769  }
770  else
771  {
772  filters[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
773  }
774  attrs << mReferencedLayer->fields().lookupField( fieldName );
775  }
776  }
777 
778  if ( mChainFilters )
779  {
780  QComboBox *ccb = nullptr;
781  const auto constMFilterComboBoxes = mFilterComboBoxes;
782  for ( QComboBox *cb : constMFilterComboBoxes )
783  {
784  if ( !ccb )
785  {
786  if ( cb == scb )
787  ccb = cb;
788 
789  continue;
790  }
791 
792  if ( ccb->currentIndex() != 0 )
793  {
794  const QString fieldName = cb->property( "Field" ).toString();
795 
796  cb->blockSignals( true );
797  cb->clear();
798  cb->addItem( cb->property( "FieldAlias" ).toString() );
799 
800  // ccb = scb
801  // cb = scb + 1
802  QStringList texts;
803  const auto txts { mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] };
804  for ( const QString &txt : txts )
805  {
806  QMap<QString, QString> filtersAttrs = filters;
807  filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt );
808  QgsAttributeList subset = attrs;
809 
810  QString expression = filterExpression;
811  if ( ! filterExpression.isEmpty() && ! filtersAttrs.values().isEmpty() )
812  expression += QLatin1String( " AND " );
813 
814  expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral( " ( " );
815  expression += filtersAttrs.values().join( QLatin1String( " AND " ) );
816  expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral( " ) " );
817 
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 
847  if ( ! filterExpression.isEmpty() && ! filters.values().isEmpty() )
848  filterExpression += QLatin1String( " AND " );
849 
850  filterExpression += filters.isEmpty() ? QString() : QStringLiteral( " ( " );
851  filterExpression += filters.values().join( QLatin1String( " AND " ) );
852  filterExpression += filters.isEmpty() ? QString() : QStringLiteral( " ) " );
853 
855 }
856 
857 void QgsRelationReferenceWidget::addEntry()
858 {
859  if ( !mReferencedLayer )
860  return;
861 
862  const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
863  if ( !tools )
864  return;
865  if ( !mCanvas )
866  return;
867 
868  // no geometry, skip the digitizing
869  if ( mReferencedLayer->geometryType() == QgsWkbTypes::UnknownGeometry || mReferencedLayer->geometryType() == QgsWkbTypes::NullGeometry )
870  {
871  QgsFeature f( mReferencedLayer->fields() );
872  entryAdded( f );
873  return;
874  }
875 
876  mMapToolDigitize->setLayer( mReferencedLayer );
877  setMapTool( mMapToolDigitize );
878 
879  connect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationReferenceWidget::entryAdded );
880  connect( mCanvas, &QgsMapCanvas::keyPressed, this, &QgsRelationReferenceWidget::onKeyPressed );
881 
882  if ( mMessageBar )
883  {
884  QString title = tr( "Relation %1 for %2." ).arg( mRelation.name(), mReferencingLayer->name() );
885 
886  QString displayString = QgsVectorLayerUtils::getFeatureDisplayString( mReferencingLayer, mFormFeature );
887  QString msg = tr( "Link feature to %1 \"%2\" : Digitize the geometry for the new feature on layer %3. Press &lt;ESC&gt; to cancel." )
888  .arg( mReferencingLayer->name(), displayString, mReferencedLayer->name() );
889  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
890  mMessageBar->pushItem( mMessageBarItem );
891  }
892 
893 }
894 
895 void QgsRelationReferenceWidget::entryAdded( const QgsFeature &feat )
896 {
897  QgsFeature f( feat );
898  QgsAttributeMap attributes;
899 
900  // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
901  if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
902  {
903  int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
904 
905  if ( fieldIdx != -1 )
906  {
907  attributes.insert( fieldIdx, mComboBox->currentText() );
908  }
909  }
910 
911  if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, f.geometry(), &f, this, false, true ) )
912  {
913  QVariantList attrs;
914  for ( const QString &fieldName : std::as_const( mReferencedFields ) )
915  attrs << f.attribute( fieldName );
916 
917  setForeignKeys( attrs );
918 
919  mAddEntryButton->setEnabled( false );
920  }
921 
922  unsetMapTool();
923 }
924 
925 void QgsRelationReferenceWidget::updateAddEntryButton()
926 {
927  mAddEntryButton->setVisible( mAllowAddFeatures && mMapToolDigitize );
928  mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
929 }
930 
931 void QgsRelationReferenceWidget::disableChainedComboBoxes( const QComboBox *scb )
932 {
933  QComboBox *ccb = nullptr;
934  const auto constMFilterComboBoxes = mFilterComboBoxes;
935  for ( QComboBox *cb : constMFilterComboBoxes )
936  {
937  if ( !ccb )
938  {
939  if ( cb == scb )
940  {
941  ccb = cb;
942  }
943 
944  continue;
945  }
946 
947  cb->setCurrentIndex( 0 );
948  if ( ccb->currentIndex() == 0 )
949  {
950  cb->setEnabled( false );
951  }
952 
953  ccb = cb;
954  }
955 }
956 
957 void QgsRelationReferenceWidget::emitForeignKeysChanged( const QVariantList &foreignKeys, bool force )
958 {
959  if ( foreignKeys == mForeignKeys && force == false && qVariantListIsNull( foreignKeys ) == qVariantListIsNull( mForeignKeys ) )
960  return;
961 
962  mForeignKeys = foreignKeys;
964  emit foreignKeyChanged( foreignKeys.at( 0 ) );
967 }
968 
970 {
971  return mReferencedLayerName;
972 }
973 
974 void QgsRelationReferenceWidget::setReferencedLayerName( const QString &relationLayerName )
975 {
976  mReferencedLayerName = relationLayerName;
977 }
978 
980 {
981  return mReferencedLayerId;
982 }
983 
984 void QgsRelationReferenceWidget::setReferencedLayerId( const QString &relationLayerId )
985 {
986  mReferencedLayerId = relationLayerId;
987 }
988 
990 {
991  return mReferencedLayerProviderKey;
992 }
993 
994 void QgsRelationReferenceWidget::setReferencedLayerProviderKey( const QString &relationProviderKey )
995 {
996  mReferencedLayerProviderKey = relationProviderKey;
997 }
998 
1000 {
1001  return mReferencedLayerDataSource;
1002 }
1003 
1004 void QgsRelationReferenceWidget::setReferencedLayerDataSource( const QString &relationDataSource )
1005 {
1006  const QgsPathResolver resolver { QgsProject::instance()->pathResolver() };
1007  mReferencedLayerDataSource = resolver.writePath( relationDataSource );
1008 }
1009 
1011 {
1012  mFormFeature = formFeature;
1013 }
void reset(T *p=nullptr)
Will reset the managed pointer to p.
static QString nullRepresentation()
This string is used to represent the value NULL throughout QGIS.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
void show()
Show the dialog non-blocking. Reparents this dialog to be a child of the dialog form.
This class contains context information for attribute editor widgets.
QgsAdvancedDigitizingDockWidget * cadDockWidget() const
Returns the associated CAD dock widget (e.g.
@ Single
When showing a single feature (e.g. district information when looking at the form of a house)
@ Embed
A form was embedded as a widget on another form.
@ StandaloneDialog
A form was opened as a new dialog.
const QgsVectorLayerTools * vectorLayerTools() const
Returns the associated vector layer tools.
void setFeature(const QgsFeature &feature)
Update all editors to correspond to a different feature.
A groupbox that collapses/expands when toggled and can save its collapsed and checked states.
static QString createFieldEqualityExpression(const QString &fieldName, const QVariant &value, QVariant::Type fieldType=QVariant::Type::Invalid)
Create an expression allowing to evaluate if a field is equal to a value.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This offers a combobox with autocompleter that allows selecting features from a layer.
void setIdentifierValues(const QVariantList &identifierValues)
The identifier values of the currently selected feature.
void setDisplayExpression(const QString &displayExpression)
The display expression will be used to display features as well as the value to match the typed text ...
void setFilterExpression(const QString &filterExpression)
An additional expression to further restrict the available features.
void setIdentifierFields(const QStringList &identifierFields)
Field name that will be used to uniquely identify the current feature.
void setSourceLayer(QgsVectorLayer *sourceLayer)
The layer from which features should be listed.
QgsFeatureRequest currentFeatureRequest() const
Shorthand for getting a feature request to query the currently selected feature.
void setIdentifierValuesToNull()
Sets the identifier values of the currently selected feature to NULL value(s).
void setCurrentFeature(const QgsFeature &feature)
Sets the current index by using the given feature.
void currentFeatureChanged()
Emitted when the current feature changes.
void setAllowNull(bool allowNull)
Determines if a NULL value should be available in the list.
This class wraps a request for features to a vector layer (or directly its vector data provider).
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:223
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:209
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:320
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
QVariant::Type type
Definition: qgsfield.h:58
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
QgsGeometry centroid() const
Returns the center of mass of a geometry.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
A class for highlight features on the map.
Definition: qgshighlight.h:62
static void styleHighlight(QgsHighlight *highlight)
Applies style from the settings to the highlight.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:90
void setExtent(const QgsRectangle &r, bool magnified=false)
Sets the extent of the map canvas to the specified rectangle.
void zoomByFactor(double scaleFactor, const QgsPointXY *center=nullptr, bool ignoreScaleLock=false)
Zoom with the factor supplied.
void unsetMapTool(QgsMapTool *mapTool)
Unset the current map tool or last non zoom tool.
void keyPressed(QKeyEvent *e)
Emit key press event.
void setMapTool(QgsMapTool *mapTool, bool clean=false)
Sets the map tool currently being used on the canvas.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
QgsRectangle extent() const
Returns the current zoom extent of the map canvas.
void refresh()
Repaints the canvas map.
QString name
Definition: qgsmaplayer.h:76
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
QString providerType() const
Returns the provider type (provider key) for this layer.
QString publicSource() const
Gets a version of the internal layer definition that has sensitive bits removed (for example,...
void editingStarted()
Emitted when editing on this layer has started.
QgsPointXY layerToMapCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from layer's CRS to output CRS
This tool digitizes geometry of new point/line/polygon features on already existing vector layers Onc...
void digitizingCompleted(const QgsFeature &feature)
Emitted whenever the digitizing has been successfully completed.
void setLayer(QgsMapLayer *vl)
Change the layer edited by the map tool.
The QgsMapToolIdentifyFeature class is a map tool to identify a feature on a chosen layer.
void featureIdentified(const QgsFeature &)
void setLayer(QgsVectorLayer *vl)
change the layer used by the map tool to identify
Abstract base class for all map tools.
Definition: qgsmaptool.h:71
void deactivated()
signal emitted once the map tool is deactivated
void setButton(QAbstractButton *button)
Use this to associate a button to this maptool.
Definition: qgsmaptool.cpp:150
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:61
bool popWidget(QgsMessageBarItem *item)
Remove the specified item from the bar, and display the next most recent one in the stack.
void pushItem(QgsMessageBarItem *item)
Display a message item on the bar, after hiding the currently visible one and putting it in a stack.
static QgsMessageBarItem * createMessage(const QString &text, QWidget *parent=nullptr)
Creates message bar item widget containing a message text to be displayed on the bar.
Resolves relative paths into absolute paths and vice versa.
A class to represent a 2D point.
Definition: qgspointxy.h:59
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:479
QgsPathResolver pathResolver() const
Returns path resolver object with considering whether the project uses absolute or relative paths and...
A rectangle specified with double values.
Definition: qgsrectangle.h:42
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
Definition: qgsrectangle.h:256
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:391
bool contains(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
void showEvent(QShowEvent *e) override
void setFilterExpression(const QString &filterExpression)
If not empty, will be used as filter expression.
QString filterExpression() const
Returns the currently set filter expression.
void setReferencedLayerProviderKey(const QString &referencedLayerProviderKey)
Set the data provider key of the referenced layer to referencedLayerProviderKey.
QString referencedLayerDataSource() const
Returns the public data source of the referenced layer.
void foreignKeysChanged(const QVariantList &)
Emitted when the foreign keys changed.
bool allowAddFeatures() const
Determines if a button for adding new features should be shown.
Q_DECL_DEPRECATED void setForeignKey(const QVariant &value)
this sets the related feature using from the foreign key
QString referencedLayerProviderKey() const
Returns the data provider key of the referenced layer.
void setChainFilters(bool chainFilters)
Set if filters are chained.
QString referencedLayerId() const
Returns the id of the referenced layer.
void setEditorContext(const QgsAttributeEditorContext &context, QgsMapCanvas *canvas, QgsMessageBar *messageBar)
Sets the editor context.
void setReferencedLayerName(const QString &referencedLayerName)
Set the name of the referenced layer to referencedLayerName.
void showIndeterminateState()
Sets the widget to display in an indeterminate "mixed value" state.
void openForm()
open the form of the related feature in a new dialog
void setReferencedLayerDataSource(const QString &referencedLayerDataSource)
Set the public data source of the referenced layer to referencedLayerDataSource.
void setFilterFields(const QStringList &filterFields)
Sets the fields for which filter comboboxes will be created.
Q_DECL_DEPRECATED void foreignKeyChanged(const QVariant &)
Emitted when the foreign key changed.
void setAllowMapIdentification(bool allowMapIdentification)
bool allowMapIdentification()
determines if the widget offers the possibility to select the related feature on the map (using a ded...
bool orderByValue()
If the widget will order the combobox entries by value.
QgsRelation relation() const
Returns the current relation, which might be invalid.
void setReferencedLayerId(const QString &referencedLayerId)
Set the id of the referenced layer to referencedLayerId.
bool chainFilters() const
Determines if the filters are chained.
QVariantList foreignKeys() const
returns the related feature foreign key
void setForeignKeys(const QVariantList &values)
Sets the related feature using the foreign keys.
Q_DECL_DEPRECATED QVariant foreignKey() const
returns the related feature foreign key
void setOrderByValue(bool orderByValue)
Sets if the widget will order the combobox entries by value.
void setRelation(const QgsRelation &relation, bool allowNullValue)
void setOpenFormButtonVisible(bool openFormButtonVisible)
QgsFeature referencedFeature() const
Returns the related feature (from the referenced layer) if no feature is related, it returns an inval...
void mapIdentification()
activate the map tool to select a new related feature on the map
void setAllowAddFeatures(bool allowAddFeatures)
Determines if a button for adding new features should be shown.
void deleteForeignKeys()
unset the currently related feature
QString referencedLayerName() const
Returns the name of the referenced layer.
void setFormFeature(const QgsFeature &formFeature)
Set the current form feature (from the referencing layer)
Defines a relation between matching fields of the two involved tables of a relation.
Definition: qgsrelation.h:89
QString name
Definition: qgsrelation.h:49
QgsVectorLayer * referencedLayer
Definition: qgsrelation.h:48
QList< QgsRelation::FieldPair > fieldPairs() const
Returns the field pairs which form this relation The first element of each pair are the field names o...
QgsVectorLayer * referencingLayer
Definition: qgsrelation.h:47
bool isValid
Definition: qgsrelation.h:50
QString referencedLayerId() const
Access the referenced (parent) layer's id.
Methods in this class are used to handle basic operations on vector layers.
virtual bool addFeature(QgsVectorLayer *layer, const QgsAttributeMap &defaultValues=QgsAttributeMap(), const QgsGeometry &defaultGeometry=QgsGeometry(), QgsFeature *feature=nullptr, QWidget *parentWidget=nullptr, bool showModal=true, bool hideParent=false) const =0
This method should/will be called, whenever a new feature will be added to the layer.
static QString getFeatureDisplayString(const QgsVectorLayer *layer, const QgsFeature &feature)
QString attributeDisplayName(int index) const
Convenience function that returns the attribute alias if defined or the field name else.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
QString displayExpression
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
CORE_EXPORT QgsMeshVertex centroid(const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Returns the centroid of the face.
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:119
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:2815
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:2814
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:2186
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:38
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
QList< int > QgsAttributeList
Definition: qgsfield.h:26
bool qVariantListIsNull(const QVariantList &list)