QGIS API Documentation  2.99.0-Master (19b062c)
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  mComboBox->setSourceLayer( mReferencedLayer );
505  mComboBox->setDisplayExpression( mReferencedLayer->displayExpression() );
506  mComboBox->setAllowNull( mAllowNull );
507  mComboBox->setIdentifierField( mReferencedField );
508 
509  QVariant nullValue = QgsApplication::nullRepresentation();
510 
511  if ( mChainFilters && mFeature.isValid() )
512  {
513  for ( int i = 0; i < mFilterFields.size(); i++ )
514  {
515  QVariant v = mFeature.attribute( mFilterFields[i] );
516  QString f = v.isNull() ? nullValue.toString() : v.toString();
517  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
518  }
519  }
520 
521  // Only connect after iterating, to have only one iterator on the referenced table at once
522  connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::comboReferenceChanged );
523  updateAttributeEditorFrame( mFeature );
524  QApplication::restoreOverrideCursor();
525  }
526 }
527 
528 void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
529 {
530  if ( action == mHighlightFeatureAction )
531  {
532  highlightFeature();
533  }
534  else if ( action == mScaleHighlightFeatureAction )
535  {
536  highlightFeature( QgsFeature(), Scale );
537  }
538  else if ( action == mPanHighlightFeatureAction )
539  {
540  highlightFeature( QgsFeature(), Pan );
541  }
542 }
543 
545 {
546  QgsFeature feat = referencedFeature();
547 
548  if ( !feat.isValid() )
549  return;
550 
552  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
553  attributeDialog.exec();
554 }
555 
556 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
557 {
558  if ( !mCanvas )
559  return;
560 
561  if ( !f.isValid() )
562  {
563  f = referencedFeature();
564  if ( !f.isValid() )
565  return;
566  }
567 
568  if ( !f.hasGeometry() )
569  {
570  return;
571  }
572 
573  QgsGeometry geom = f.geometry();
574 
575  // scale or pan
576  if ( canvasExtent == Scale )
577  {
578  QgsRectangle featBBox = geom.boundingBox();
579  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
580  QgsRectangle extent = mCanvas->extent();
581  if ( !extent.contains( featBBox ) )
582  {
583  extent.combineExtentWith( featBBox );
584  extent.scale( 1.1 );
585  mCanvas->setExtent( extent );
586  mCanvas->refresh();
587  }
588  }
589  else if ( canvasExtent == Pan )
590  {
591  QgsGeometry centroid = geom.centroid();
592  QgsPointXY center = centroid.asPoint();
593  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
594  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
595  }
596 
597  // highlight
598  deleteHighlight();
599  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
600  QgsSettings settings;
601  QColor color = QColor( settings.value( QStringLiteral( "Map/highlight/color" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
602  int alpha = settings.value( QStringLiteral( "Map/highlight/colorAlpha" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
603  double buffer = settings.value( QStringLiteral( "Map/highlight/buffer" ), Qgis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
604  double minWidth = settings.value( QStringLiteral( "Map/highlight/minWidth" ), Qgis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
605 
606  mHighlight->setColor( color ); // sets also fill with default alpha
607  color.setAlpha( alpha );
608  mHighlight->setFillColor( color ); // sets fill with alpha
609  mHighlight->setBuffer( buffer );
610  mHighlight->setMinWidth( minWidth );
611  mHighlight->show();
612 
613  QTimer *timer = new QTimer( this );
614  timer->setSingleShot( true );
615  connect( timer, &QTimer::timeout, this, &QgsRelationReferenceWidget::deleteHighlight );
616  timer->start( 3000 );
617 }
618 
619 void QgsRelationReferenceWidget::deleteHighlight()
620 {
621  if ( mHighlight )
622  {
623  mHighlight->hide();
624  delete mHighlight;
625  }
626  mHighlight = nullptr;
627 }
628 
630 {
631  if ( !mAllowMapIdentification || !mReferencedLayer )
632  return;
633 
634  const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
635  if ( !tools )
636  return;
637  if ( !mCanvas )
638  return;
639 
640  mMapTool->setLayer( mReferencedLayer );
641  mCanvas->setMapTool( mMapTool );
642 
643  mWindowWidget = window();
644 
645  mCanvas->window()->raise();
646  mCanvas->activateWindow();
647  mCanvas->setFocus();
648 
649  connect( mMapTool, static_cast<void ( QgsMapToolIdentifyFeature::* )( const QgsFeature & )>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
650  connect( mMapTool, &QgsMapTool::deactivated, this, &QgsRelationReferenceWidget::mapToolDeactivated );
651 
652  if ( mMessageBar )
653  {
654  QString title = tr( "Relation %1 for %2." ).arg( mRelationName, mReferencingLayer->name() );
655  QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
656  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
657  mMessageBar->pushItem( mMessageBarItem );
658  }
659 }
660 
661 void QgsRelationReferenceWidget::comboReferenceChanged( int index )
662 {
663  Q_UNUSED( index )
664  mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( mFeature );
665  highlightFeature( mFeature );
666  updateAttributeEditorFrame( mFeature );
667  emit foreignKeyChanged( mFeature.attribute( mReferencedFieldIdx ) );
668 }
669 
670 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature &feature )
671 {
672  mOpenFormButton->setEnabled( feature.isValid() );
673  // Check if we're running with an embedded frame we need to update
674  if ( mAttributeEditorFrame && mReferencedAttributeForm )
675  {
676  mReferencedAttributeForm->setFeature( feature );
677  }
678 }
679 
681 {
682  return mAllowAddFeatures;
683 }
684 
686 {
687  mAllowAddFeatures = allowAddFeatures;
688  updateAddEntryButton();
689 }
690 
691 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature &feature )
692 {
693  if ( mReadOnlySelector )
694  {
695  QgsExpression expr( mReferencedLayer->displayExpression() );
697  context.setFeature( feature );
698  QString title = expr.evaluate( &context ).toString();
699  if ( expr.hasEvalError() )
700  {
701  title = feature.attribute( mReferencedFieldIdx ).toString();
702  }
703  mLineEdit->setText( title );
704  mForeignKey = feature.attribute( mReferencedFieldIdx );
705  mFeature = feature;
706  }
707  else
708  {
709  mComboBox->setCurrentIndex( mComboBox->findData( feature.id(), QgsAttributeTableModel::FeatureIdRole ) );
710  mFeature = feature;
711  }
712 
713  mRemoveFKButton->setEnabled( mIsEditable );
714  highlightFeature( feature );
715  updateAttributeEditorFrame( feature );
716  emit foreignKeyChanged( foreignKey() );
717 
718  unsetMapTool();
719 }
720 
721 void QgsRelationReferenceWidget::unsetMapTool()
722 {
723  // deactivate map tool if activated
724  if ( mCanvas && mMapTool )
725  {
726  /* this will call mapToolDeactivated */
727  mCanvas->unsetMapTool( mMapTool );
728  }
729 }
730 
731 void QgsRelationReferenceWidget::mapToolDeactivated()
732 {
733  if ( mWindowWidget )
734  {
735  mWindowWidget->raise();
736  mWindowWidget->activateWindow();
737  }
738 
739  if ( mMessageBar && mMessageBarItem )
740  {
741  mMessageBar->popWidget( mMessageBarItem );
742  }
743  mMessageBarItem = nullptr;
744 }
745 
746 void QgsRelationReferenceWidget::filterChanged()
747 {
748  QVariant nullValue = QgsApplication::nullRepresentation();
749 
750  QMap<QString, QString> filters;
751  QgsAttributeList attrs;
752 
753  QComboBox *scb = qobject_cast<QComboBox *>( sender() );
754 
755  Q_ASSERT( scb );
756 
757  QgsFeature f;
758  QgsFeatureIds featureIds;
759  QString filterExpression;
760 
761  // comboboxes have to be disabled before building filters
762  if ( mChainFilters )
763  disableChainedComboBoxes( scb );
764 
765  // build filters
766  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
767  {
768  if ( cb->currentIndex() != 0 )
769  {
770  const QString fieldName = cb->property( "Field" ).toString();
771 
772  if ( cb->currentText() == nullValue.toString() )
773  {
774  filters[fieldName] = QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
775  }
776  else
777  {
778  filters[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
779  }
780  attrs << mReferencedLayer->fields().lookupField( fieldName );
781  }
782  }
783 
784  if ( mChainFilters )
785  {
786  QComboBox *ccb = nullptr;
787  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
788  {
789  if ( !ccb )
790  {
791  if ( cb == scb )
792  ccb = cb;
793 
794  continue;
795  }
796 
797  if ( ccb->currentIndex() != 0 )
798  {
799  const QString fieldName = cb->property( "Field" ).toString();
800 
801  cb->blockSignals( true );
802  cb->clear();
803  cb->addItem( cb->property( "FieldAlias" ).toString() );
804 
805  // ccb = scb
806  // cb = scb + 1
807  QStringList texts;
808  Q_FOREACH ( const QString &txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
809  {
810  QMap<QString, QString> filtersAttrs = filters;
811  filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt );
812  QString expression = filtersAttrs.values().join( QStringLiteral( " AND " ) );
813 
814  QgsAttributeList subset = attrs;
815  subset << mReferencedLayer->fields().lookupField( fieldName );
816 
817  QgsFeatureIterator it( mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
818 
819  bool found = false;
820  while ( it.nextFeature( f ) )
821  {
822  if ( !featureIds.contains( f.id() ) )
823  featureIds << f.id();
824 
825  found = true;
826  }
827 
828  // item is only provided if at least 1 feature exists
829  if ( found )
830  texts << txt;
831  }
832 
833  texts.sort();
834  cb->addItems( texts );
835 
836  cb->setEnabled( true );
837  cb->blockSignals( false );
838 
839  ccb = cb;
840  }
841  }
842  }
843  filterExpression = filters.values().join( QStringLiteral( " AND " ) );
844  mComboBox->setFilterExpression( filterExpression );
845 }
846 
847 void QgsRelationReferenceWidget::addEntry()
848 {
849  QgsFeature f( mReferencedLayer->fields() );
850  QgsAttributeMap attributes;
851 
852  // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
853  if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
854  {
855  int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
856 
857  if ( fieldIdx != -1 )
858  {
859  attributes.insert( fieldIdx, mComboBox->currentText() );
860  }
861  }
862 
863  if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, QgsGeometry(), &f ) )
864  {
865  mComboBox->setIdentifierValue( f.attribute( mReferencingFieldIdx ) );
866  mAddEntryButton->setEnabled( false );
867  }
868 }
869 
870 void QgsRelationReferenceWidget::updateAddEntryButton()
871 {
872  mAddEntryButton->setVisible( mAllowAddFeatures );
873  mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
874 }
875 
876 void QgsRelationReferenceWidget::disableChainedComboBoxes( const QComboBox *scb )
877 {
878  QComboBox *ccb = nullptr;
879  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
880  {
881  if ( !ccb )
882  {
883  if ( cb == scb )
884  {
885  ccb = cb;
886  }
887 
888  continue;
889  }
890 
891  cb->setCurrentIndex( 0 );
892  if ( ccb->currentIndex() == 0 )
893  {
894  cb->setEnabled( false );
895  }
896 
897  ccb = cb;
898  }
899 }
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)
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
QgsField at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:145
Get the feature id of the feature in this row.
void setBuffer(double buffer)
Set line / stroke buffer in millimeters.
Definition: qgshighlight.h:96
void setOrderByValue(bool orderByValue)
Set if the widget will order the combobox entries by value.
QgsFeatureRequest & setFilterFid(QgsFeatureId fid)
Set feature ID that should be fetched.
static QgsMessageBarItem * createMessage(const QString &text, QWidget *parent=nullptr)
make out a widget containing a message to be displayed on the bar
void setMapTool(QgsMapTool *mapTool, bool clean=false)
Sets the map tool currently being used on the canvas.
QgsFeatureRequest getReferencedFeatureRequest(const QgsAttributes &attributes) const
Creates a request to return the feature on the referenced (parent) layer which is referenced by the p...
QgsFields fields() const override
Returns the list of fields of this layer.
void setFeature(const QgsFeature &feature)
Update all editors to correspond to a different feature.
bool popWidget(QgsMessageBarItem *item)
Remove the passed widget from the bar (if previously added), then display the next one in the stack i...
void setAllowNull(bool allowNull)
Determines if a NULL value should be available in the list.
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 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