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