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