QGIS API Documentation  2.99.0-Master (a411669)
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 
44  : QWidget( parent )
45  , mEditorContext( QgsAttributeEditorContext() )
46  , mReferencedFieldIdx( -1 )
47  , mReferencingFieldIdx( -1 )
48  , mAllowNull( true )
49  , mShown( false )
50  , mIsEditable( true )
51  , mEmbedForm( false )
52  , mReadOnlySelector( false )
53  , mAllowMapIdentification( false )
54  , mOrderByValue( false )
55  , mOpenFormButtonVisible( true )
56  , mChainFilters( false )
57  , mAllowAddFeatures( false )
58 {
59  mTopLayout = new QVBoxLayout( this );
60  mTopLayout->setContentsMargins( 0, 0, 0, 0 );
61 
62  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::Fixed );
63 
64  setLayout( mTopLayout );
65 
66  QHBoxLayout *editLayout = new QHBoxLayout();
67  editLayout->setContentsMargins( 0, 0, 0, 0 );
68  editLayout->setSpacing( 2 );
69 
70  // Prepare the container and layout for the filter comboboxes
71  mChooserContainer = new QWidget;
72  editLayout->addWidget( mChooserContainer );
73  QHBoxLayout *chooserLayout = new QHBoxLayout;
74  chooserLayout->setContentsMargins( 0, 0, 0, 0 );
75  mFilterLayout = new QHBoxLayout;
76  mFilterLayout->setContentsMargins( 0, 0, 0, 0 );
77  mFilterContainer = new QWidget;
78  mFilterContainer->setLayout( mFilterLayout );
79  mChooserContainer->setLayout( chooserLayout );
80  chooserLayout->addWidget( mFilterContainer );
81 
82  // combobox (for non-geometric relation)
83  mComboBox = new QComboBox();
84  mChooserContainer->layout()->addWidget( mComboBox );
85 
86  // read-only line edit
87  mLineEdit = new QLineEdit();
88  mLineEdit->setReadOnly( true );
89  editLayout->addWidget( mLineEdit );
90 
91  // open form button
92  mOpenFormButton = new QToolButton();
93  mOpenFormButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPropertyItem.svg" ) ) );
94  mOpenFormButton->setText( tr( "Open related feature form" ) );
95  editLayout->addWidget( mOpenFormButton );
96 
97  mAddEntryButton = new QToolButton();
98  mAddEntryButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAdd.svg" ) ) );
99  mAddEntryButton->setText( tr( "Add new entry" ) );
100  editLayout->addWidget( mAddEntryButton );
101 
102  // highlight button
103  mHighlightFeatureButton = new QToolButton( this );
104  mHighlightFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
105  mHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHighlightFeature.svg" ) ), tr( "Highlight feature" ), this );
106  mScaleHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleHighlightFeature.svg" ) ), tr( "Scale and highlight feature" ), this );
107  mPanHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPanHighlightFeature.svg" ) ), tr( "Pan and highlight feature" ), this );
108  mHighlightFeatureButton->addAction( mHighlightFeatureAction );
109  mHighlightFeatureButton->addAction( mScaleHighlightFeatureAction );
110  mHighlightFeatureButton->addAction( mPanHighlightFeatureAction );
111  mHighlightFeatureButton->setDefaultAction( mHighlightFeatureAction );
112  editLayout->addWidget( mHighlightFeatureButton );
113 
114  // map identification button
115  mMapIdentificationButton = new QToolButton( this );
116  mMapIdentificationButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMapIdentification.svg" ) ) );
117  mMapIdentificationButton->setText( tr( "Select on map" ) );
118  mMapIdentificationButton->setCheckable( true );
119  editLayout->addWidget( mMapIdentificationButton );
120 
121  // remove foreign key button
122  mRemoveFKButton = new QToolButton( this );
123  mRemoveFKButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemove.svg" ) ) );
124  mRemoveFKButton->setText( tr( "No selection" ) );
125  editLayout->addWidget( mRemoveFKButton );
126 
127  // add line to top layout
128  mTopLayout->addLayout( editLayout );
129 
130  // embed form
131  mAttributeEditorFrame = new QgsCollapsibleGroupBox( this );
132  mAttributeEditorLayout = new QVBoxLayout( mAttributeEditorFrame );
133  mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
134  mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
135  mTopLayout->addWidget( mAttributeEditorFrame );
136 
137  // invalid label
138  mInvalidLabel = new QLabel( tr( "The relation is not valid. Please make sure your relation definitions are OK." ) );
139  mInvalidLabel->setWordWrap( true );
140  QFont font = mInvalidLabel->font();
141  font.setItalic( true );
142  mInvalidLabel->setStyleSheet( QStringLiteral( "QLabel { color: red; } " ) );
143  mInvalidLabel->setFont( font );
144  mTopLayout->addWidget( mInvalidLabel );
145 
146  // default mode is combobox, no geometric relation and no embed form
147  mLineEdit->hide();
148  mMapIdentificationButton->hide();
149  mHighlightFeatureButton->hide();
150  mAttributeEditorFrame->hide();
151  mInvalidLabel->hide();
152 
153  // connect buttons
154  connect( mOpenFormButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::openForm );
155  connect( mHighlightFeatureButton, &QToolButton::triggered, this, &QgsRelationReferenceWidget::highlightActionTriggered );
156  connect( mMapIdentificationButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::mapIdentification );
157  connect( mRemoveFKButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::deleteForeignKey );
158  connect( mAddEntryButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::addEntry );
159  connect( mComboBox, &QComboBox::editTextChanged, this, &QgsRelationReferenceWidget::updateAddEntryButton );
160 }
161 
163 {
164  deleteHighlight();
165  unsetMapTool();
166  if ( mMapTool )
167  delete mMapTool;
168 }
169 
170 void QgsRelationReferenceWidget::setRelation( const QgsRelation &relation, bool allowNullValue )
171 {
172  mAllowNull = allowNullValue;
173  mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
174 
175  if ( relation.isValid() )
176  {
177  mInvalidLabel->hide();
178 
179  mRelation = relation;
180  mReferencingLayer = relation.referencingLayer();
181  mRelationName = relation.name();
182  mReferencedLayer = relation.referencedLayer();
183  mReferencedFieldIdx = mReferencedLayer->fields().lookupField( relation.fieldPairs().at( 0 ).second );
184  mReferencingFieldIdx = mReferencingLayer->fields().lookupField( relation.fieldPairs().at( 0 ).first );
185  mAttributeEditorFrame->setObjectName( QStringLiteral( "referencing/" ) + relation.name() );
186 
187 
188  if ( mEmbedForm )
189  {
191  mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
192  mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
193  mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
194  }
195 
196  connect( mReferencedLayer, &QgsVectorLayer::editingStarted, this, &QgsRelationReferenceWidget::updateAddEntryButton );
197  connect( mReferencedLayer, &QgsVectorLayer::editingStopped, this, &QgsRelationReferenceWidget::updateAddEntryButton );
198  updateAddEntryButton();
199  }
200  else
201  {
202  mInvalidLabel->show();
203  }
204 
205  if ( mShown && isVisible() )
206  {
207  init();
208  }
209 }
210 
212 {
213  if ( !editable )
214  unsetMapTool();
215 
216  mFilterContainer->setEnabled( editable );
217  mComboBox->setEnabled( editable );
218  mComboBox->setEditable( true );
219  mMapIdentificationButton->setEnabled( editable );
220  mRemoveFKButton->setEnabled( editable );
221  mIsEditable = editable;
222 }
223 
224 void QgsRelationReferenceWidget::setForeignKey( const QVariant &value )
225 {
226  if ( !value.isValid() )
227  {
228  return;
229  }
230  if ( value.isNull() )
231  {
233  return;
234  }
235 
236  if ( !mReferencedLayer )
237  return;
238 
239  // Attributes from the referencing layer
240  QgsAttributes attrs = QgsAttributes( mReferencingLayer->fields().count() );
241  // Set the value on the foreign key field of the referencing record
242  attrs[ mReferencingLayer->fields().lookupField( mRelation.fieldPairs().at( 0 ).first )] = value;
243 
244  QgsFeatureRequest request = mRelation.getReferencedFeatureRequest( attrs );
245 
246  mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
247 
248  if ( !mFeature.isValid() )
249  {
250  return;
251  }
252 
253  mForeignKey = mFeature.attribute( mReferencedFieldIdx );
254 
255  if ( mReadOnlySelector )
256  {
257  QgsExpression expr( mReferencedLayer->displayExpression() );
259  context.setFeature( mFeature );
260  QString title = expr.evaluate( &context ).toString();
261  if ( expr.hasEvalError() )
262  {
263  title = mFeature.attribute( mReferencedFieldIdx ).toString();
264  }
265  mLineEdit->setText( title );
266  }
267  else
268  {
269  QVariant nullValue = QgsApplication::nullRepresentation();
270 
271  if ( mChainFilters && mFeature.isValid() && mFilterComboBoxes.count() >= mFilterFields.count() )
272  {
273  QgsFeature feature = mFeature;
274 
275  for ( int i = 0; i < mFilterFields.size(); i++ )
276  {
277  QVariant v = feature.attribute( mFilterFields[i] );
278  QString f = v.isNull() ? nullValue.toString() : v.toString();
279  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
280  }
281  }
282 
283  int i = mComboBox->findData( mFeature.id(), QgsAttributeTableModel::FeatureIdRole );
284  if ( i == -1 && mAllowNull )
285  {
286  mComboBox->setCurrentIndex( 0 );
287  }
288  else
289  {
290  mComboBox->setCurrentIndex( i );
291  }
292  }
293 
294  mRemoveFKButton->setEnabled( mIsEditable );
295  highlightFeature( mFeature );
296  updateAttributeEditorFrame( mFeature );
297  emit foreignKeyChanged( foreignKey() );
298 }
299 
301 {
302  QVariant nullValue = QgsApplication::nullRepresentation();
303 
304  // deactivate filter comboboxes
305  if ( mChainFilters && !mFilterComboBoxes.isEmpty() )
306  {
307  QComboBox *cb = mFilterComboBoxes.first();
308  cb->setCurrentIndex( 0 );
309  disableChainedComboBoxes( cb );
310  }
311 
312  if ( mReadOnlySelector )
313  {
314  QString nullText;
315  if ( mAllowNull )
316  {
317  nullText = tr( "%1 (no selection)" ).arg( nullValue.toString() );
318  }
319  mLineEdit->setText( nullText );
320  mForeignKey = QVariant();
321  mFeature.setValid( false );
322  }
323  else
324  {
325  if ( mAllowNull )
326  {
327  mComboBox->setCurrentIndex( 0 );
328  }
329  else
330  {
331  mComboBox->setCurrentIndex( -1 );
332  }
333  }
334  mRemoveFKButton->setEnabled( false );
335  updateAttributeEditorFrame( QgsFeature() );
336  emit foreignKeyChanged( QVariant( QVariant::Int ) );
337 }
338 
340 {
341  QgsFeature f;
342  if ( mReferencedLayer )
343  {
344  QgsFeatureId fid;
345  if ( mReadOnlySelector )
346  {
347  fid = mFeature.id();
348  }
349  else
350  {
351  fid = mComboBox->currentData( QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
352  }
353  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( f );
354  }
355  return f;
356 }
357 
359 {
360  if ( mReadOnlySelector )
361  {
362  whileBlocking( mLineEdit )->setText( QString() );
363  }
364  else
365  {
366  whileBlocking( mComboBox )->setCurrentIndex( -1 );
367  }
368  mRemoveFKButton->setEnabled( false );
369  updateAttributeEditorFrame( QgsFeature() );
370 }
371 
373 {
374  if ( mReadOnlySelector )
375  {
376  return mForeignKey;
377  }
378  else
379  {
380  if ( mReferencingFieldIdx < 0 || mReferencingFieldIdx >= mReferencingLayer->fields().count() )
381  {
382  return QVariant();
383  }
384  else if ( !mFeature.isValid() )
385  {
386  return QVariant( mReferencingLayer->fields().at( mReferencingFieldIdx ).type() );
387  }
388  else
389  {
390  return mFeature.attribute( mReferencedFieldIdx );
391  }
392  }
393 }
394 
396 {
397  mEditorContext = context;
398  mCanvas = canvas;
399  mMessageBar = messageBar;
400 
401  if ( mMapTool )
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 
461  init();
462 }
463 
465 {
466  if ( !mReadOnlySelector && mComboBox->count() == 0 && mReferencedLayer )
467  {
468  QApplication::setOverrideCursor( Qt::WaitCursor );
469 
470  QSet<QString> requestedAttrs;
471 
472  QgsVectorLayerCache *layerCache = new QgsVectorLayerCache( mReferencedLayer, 100000, this );
473 
474  if ( !mFilterFields.isEmpty() )
475  {
476  Q_FOREACH ( const QString &fieldName, mFilterFields )
477  {
478  int idx = mReferencedLayer->fields().lookupField( fieldName );
479 
480  if ( idx == -1 )
481  continue;
482 
483  QComboBox *cb = new QComboBox();
484  cb->setProperty( "Field", fieldName );
485  cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
486  mFilterComboBoxes << cb;
487  QVariantList uniqueValues = mReferencedLayer->uniqueValues( idx ).toList();
488  cb->addItem( mReferencedLayer->attributeDisplayName( idx ) );
489  QVariant nullValue = QgsApplication::nullRepresentation();
490  cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->fields().at( idx ).type() ) );
491 
492  std::sort( uniqueValues.begin(), uniqueValues.end(), qgsVariantLessThan );
493  Q_FOREACH ( const QVariant &v, uniqueValues )
494  {
495  cb->addItem( v.toString(), v );
496  }
497 
498  connect( cb, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::filterChanged );
499 
500  // Request this attribute for caching
501  requestedAttrs << fieldName;
502 
503  mFilterLayout->addWidget( cb );
504  }
505 
506  if ( mChainFilters )
507  {
508  QVariant nullValue = QgsApplication::nullRepresentation();
509 
510  QgsFeature ft;
511  QgsFeatureIterator fit = layerCache->getFeatures();
512  while ( fit.nextFeature( ft ) )
513  {
514  for ( int i = 0; i < mFilterComboBoxes.count() - 1; ++i )
515  {
516  QVariant cv = ft.attribute( mFilterFields.at( i ) );
517  QVariant nv = ft.attribute( mFilterFields.at( i + 1 ) );
518  QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
519  QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
520  mFilterCache[mFilterFields[i]][cf] << nf;
521  }
522  }
523 
524  if ( !mFilterComboBoxes.isEmpty() )
525  {
526  QComboBox *cb = mFilterComboBoxes.first();
527  cb->setCurrentIndex( 0 );
528  disableChainedComboBoxes( cb );
529  }
530  }
531  }
532  else
533  {
534  mFilterContainer->hide();
535  }
536 
537  QgsExpression displayExpression( mReferencedLayer->displayExpression() );
538 
539  requestedAttrs += displayExpression.referencedColumns();
540  requestedAttrs << mRelation.fieldPairs().at( 0 ).second;
541 
542  Q_FOREACH ( const QgsConditionalStyle &style, mReferencedLayer->conditionalStyles()->rowStyles() )
543  {
544  QgsExpression exp( style.rule() );
545  requestedAttrs += exp.referencedColumns();
546  }
547 
548  if ( displayExpression.isField() )
549  {
550  Q_FOREACH ( const QgsConditionalStyle &style, mReferencedLayer->conditionalStyles()->fieldStyles( *displayExpression.referencedColumns().constBegin() ) )
551  {
552  QgsExpression exp( style.rule() );
553  requestedAttrs += exp.referencedColumns();
554  }
555  }
556 
557  QgsAttributeList attributes;
558  Q_FOREACH ( const QString &attr, requestedAttrs )
559  attributes << mReferencedLayer->fields().lookupField( attr );
560 
561  layerCache->setCacheSubsetOfAttributes( attributes );
562  mMasterModel = new QgsAttributeTableModel( layerCache, this );
563  mMasterModel->setRequest( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( requestedAttrs, mReferencedLayer->fields() ) );
564  mFilterModel = new QgsAttributeTableFilterModel( mCanvas, mMasterModel, mMasterModel );
565  mFeatureListModel = new QgsFeatureListModel( mFilterModel, this );
566  mFeatureListModel->setDisplayExpression( mReferencedLayer->displayExpression() );
567 
568  mMasterModel->loadLayer();
569 
570  mFeatureListModel->setInjectNull( mAllowNull );
571  if ( mOrderByValue )
572  {
573  mFilterModel->sort( mReferencedLayer->displayExpression() );
574  }
575 
576  mComboBox->setModel( mFeatureListModel );
577 
578  delete mComboBox->completer();
579  QCompleter *completer = new QCompleter( mComboBox->model(), mComboBox );
580  completer->setModel( mComboBox->model() );
581  completer->setFilterMode( Qt::MatchContains );
582  completer->setCaseSensitivity( Qt::CaseInsensitive );
583  mComboBox->setCompleter( completer );
584 
585 
586  QVariant nullValue = QgsApplication::nullRepresentation();
587 
588  if ( mChainFilters && mFeature.isValid() )
589  {
590  for ( int i = 0; i < mFilterFields.size(); i++ )
591  {
592  QVariant v = mFeature.attribute( mFilterFields[i] );
593  QString f = v.isNull() ? nullValue.toString() : v.toString();
594  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
595  }
596  }
597 
598  QVariant featId = mFeature.isValid() ? mFeature.id() : QVariant( QVariant::Int );
599  mComboBox->setCurrentIndex( mComboBox->findData( featId, QgsAttributeTableModel::FeatureIdRole ) );
600 
601  // Only connect after iterating, to have only one iterator on the referenced table at once
602  connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::comboReferenceChanged );
603  updateAttributeEditorFrame( mFeature );
604  QApplication::restoreOverrideCursor();
605  }
606 }
607 
608 void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
609 {
610  if ( action == mHighlightFeatureAction )
611  {
612  highlightFeature();
613  }
614  else if ( action == mScaleHighlightFeatureAction )
615  {
616  highlightFeature( QgsFeature(), Scale );
617  }
618  else if ( action == mPanHighlightFeatureAction )
619  {
620  highlightFeature( QgsFeature(), Pan );
621  }
622 }
623 
625 {
626  QgsFeature feat = referencedFeature();
627 
628  if ( !feat.isValid() )
629  return;
630 
632  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
633  attributeDialog.exec();
634 }
635 
636 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
637 {
638  if ( !mCanvas )
639  return;
640 
641  if ( !f.isValid() )
642  {
643  f = referencedFeature();
644  if ( !f.isValid() )
645  return;
646  }
647 
648  if ( !f.hasGeometry() )
649  {
650  return;
651  }
652 
653  QgsGeometry geom = f.geometry();
654 
655  // scale or pan
656  if ( canvasExtent == Scale )
657  {
658  QgsRectangle featBBox = geom.boundingBox();
659  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
660  QgsRectangle extent = mCanvas->extent();
661  if ( !extent.contains( featBBox ) )
662  {
663  extent.combineExtentWith( featBBox );
664  extent.scale( 1.1 );
665  mCanvas->setExtent( extent );
666  mCanvas->refresh();
667  }
668  }
669  else if ( canvasExtent == Pan )
670  {
671  QgsGeometry centroid = geom.centroid();
672  QgsPointXY center = centroid.asPoint();
673  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
674  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
675  }
676 
677  // highlight
678  deleteHighlight();
679  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
680  QgsSettings settings;
681  QColor color = QColor( settings.value( QStringLiteral( "Map/highlight/color" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
682  int alpha = settings.value( QStringLiteral( "Map/highlight/colorAlpha" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
683  double buffer = settings.value( QStringLiteral( "Map/highlight/buffer" ), Qgis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
684  double minWidth = settings.value( QStringLiteral( "Map/highlight/minWidth" ), Qgis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
685 
686  mHighlight->setColor( color ); // sets also fill with default alpha
687  color.setAlpha( alpha );
688  mHighlight->setFillColor( color ); // sets fill with alpha
689  mHighlight->setBuffer( buffer );
690  mHighlight->setMinWidth( minWidth );
691  mHighlight->show();
692 
693  QTimer *timer = new QTimer( this );
694  timer->setSingleShot( true );
695  connect( timer, &QTimer::timeout, this, &QgsRelationReferenceWidget::deleteHighlight );
696  timer->start( 3000 );
697 }
698 
699 void QgsRelationReferenceWidget::deleteHighlight()
700 {
701  if ( mHighlight )
702  {
703  mHighlight->hide();
704  delete mHighlight;
705  }
706  mHighlight = nullptr;
707 }
708 
710 {
711  if ( !mAllowMapIdentification || !mReferencedLayer )
712  return;
713 
714  const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
715  if ( !tools )
716  return;
717  if ( !mCanvas )
718  return;
719 
720  mMapTool->setLayer( mReferencedLayer );
721  mCanvas->setMapTool( mMapTool );
722 
723  mWindowWidget = window();
724 
725  mCanvas->window()->raise();
726  mCanvas->activateWindow();
727  mCanvas->setFocus();
728 
729  connect( mMapTool, static_cast<void ( QgsMapToolIdentifyFeature::* )( const QgsFeature & )>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
730  connect( mMapTool, &QgsMapTool::deactivated, this, &QgsRelationReferenceWidget::mapToolDeactivated );
731 
732  if ( mMessageBar )
733  {
734  QString title = tr( "Relation %1 for %2." ).arg( mRelationName, mReferencingLayer->name() );
735  QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
736  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
737  mMessageBar->pushItem( mMessageBarItem );
738  }
739 }
740 
741 void QgsRelationReferenceWidget::comboReferenceChanged( int index )
742 {
743  QgsFeatureId fid = mComboBox->itemData( index, QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
744  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( mFeature );
745  highlightFeature( mFeature );
746  updateAttributeEditorFrame( mFeature );
747  emit foreignKeyChanged( mFeature.attribute( mReferencedFieldIdx ) );
748 }
749 
750 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature &feature )
751 {
752  mOpenFormButton->setEnabled( feature.isValid() );
753  // Check if we're running with an embedded frame we need to update
754  if ( mAttributeEditorFrame && mReferencedAttributeForm )
755  {
756  mReferencedAttributeForm->setFeature( feature );
757  }
758 }
759 
761 {
762  return mAllowAddFeatures;
763 }
764 
766 {
767  mAllowAddFeatures = allowAddFeatures;
768  updateAddEntryButton();
769 }
770 
771 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature &feature )
772 {
773  if ( mReadOnlySelector )
774  {
775  QgsExpression expr( mReferencedLayer->displayExpression() );
777  context.setFeature( feature );
778  QString title = expr.evaluate( &context ).toString();
779  if ( expr.hasEvalError() )
780  {
781  title = feature.attribute( mReferencedFieldIdx ).toString();
782  }
783  mLineEdit->setText( title );
784  mForeignKey = feature.attribute( mReferencedFieldIdx );
785  mFeature = feature;
786  }
787  else
788  {
789  mComboBox->setCurrentIndex( mComboBox->findData( feature.id(), QgsAttributeTableModel::FeatureIdRole ) );
790  mFeature = feature;
791  }
792 
793  mRemoveFKButton->setEnabled( mIsEditable );
794  highlightFeature( feature );
795  updateAttributeEditorFrame( feature );
796  emit foreignKeyChanged( foreignKey() );
797 
798  unsetMapTool();
799 }
800 
801 void QgsRelationReferenceWidget::unsetMapTool()
802 {
803  // deactivate map tool if activated
804  if ( mCanvas && mMapTool )
805  {
806  /* this will call mapToolDeactivated */
807  mCanvas->unsetMapTool( mMapTool );
808  }
809 }
810 
811 void QgsRelationReferenceWidget::mapToolDeactivated()
812 {
813  if ( mWindowWidget )
814  {
815  mWindowWidget->raise();
816  mWindowWidget->activateWindow();
817  }
818 
819  if ( mMessageBar && mMessageBarItem )
820  {
821  mMessageBar->popWidget( mMessageBarItem );
822  }
823  mMessageBarItem = nullptr;
824 }
825 
826 void QgsRelationReferenceWidget::filterChanged()
827 {
828  QVariant nullValue = QgsApplication::nullRepresentation();
829 
830  QMap<QString, QString> filters;
831  QgsAttributeList attrs;
832 
833  QComboBox *scb = qobject_cast<QComboBox *>( sender() );
834 
835  Q_ASSERT( scb );
836 
837  QgsFeature f;
838  QgsFeatureIds featureIds;
839  QString filterExpression;
840 
841  // comboboxes have to be disabled before building filters
842  if ( mChainFilters )
843  disableChainedComboBoxes( scb );
844 
845  // build filters
846  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
847  {
848  if ( cb->currentIndex() != 0 )
849  {
850  const QString fieldName = cb->property( "Field" ).toString();
851 
852  if ( cb->currentText() == nullValue.toString() )
853  {
854  filters[fieldName] = QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
855  }
856  else
857  {
858  filters[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
859  }
860  attrs << mReferencedLayer->fields().lookupField( fieldName );
861  }
862  }
863 
864  bool filtered = false;
865  if ( mChainFilters )
866  {
867  QComboBox *ccb = nullptr;
868  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
869  {
870  if ( !ccb )
871  {
872  if ( cb == scb )
873  ccb = cb;
874 
875  continue;
876  }
877 
878  if ( ccb->currentIndex() != 0 )
879  {
880  const QString fieldName = cb->property( "Field" ).toString();
881  filtered = true;
882 
883  cb->blockSignals( true );
884  cb->clear();
885  cb->addItem( cb->property( "FieldAlias" ).toString() );
886 
887  // ccb = scb
888  // cb = scb + 1
889  QStringList texts;
890  Q_FOREACH ( const QString &txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
891  {
892  QMap<QString, QString> filtersAttrs = filters;
893  filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt );
894  QString expression = filtersAttrs.values().join( QStringLiteral( " AND " ) );
895 
896  QgsAttributeList subset = attrs;
897  subset << mReferencedLayer->fields().lookupField( fieldName );
898 
899  QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
900 
901  bool found = false;
902  while ( it.nextFeature( f ) )
903  {
904  if ( !featureIds.contains( f.id() ) )
905  featureIds << f.id();
906 
907  found = true;
908  }
909 
910  // item is only provided if at least 1 feature exists
911  if ( found )
912  texts << txt;
913  }
914 
915  texts.sort();
916  cb->addItems( texts );
917 
918  cb->setEnabled( true );
919  cb->blockSignals( false );
920 
921  ccb = cb;
922  }
923  }
924  }
925 
926  if ( !mChainFilters || ( mChainFilters && !filtered ) )
927  {
928  filterExpression = filters.values().join( QStringLiteral( " AND " ) );
929 
931  if ( !filterExpression.isEmpty() )
932  req.setFilterExpression( filterExpression );
933 
934  QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( req ) );
935 
936  while ( it.nextFeature( f ) )
937  {
938  featureIds << f.id();
939  }
940  }
941 
942  mFilterModel->setFilteredFeatures( featureIds );
943 }
944 
945 void QgsRelationReferenceWidget::addEntry()
946 {
947  QgsFeature f( mReferencedLayer->fields() );
948  QgsAttributeMap attributes;
949 
950  // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
951  if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
952  {
953  int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
954 
955  if ( fieldIdx != -1 )
956  {
957  attributes.insert( fieldIdx, mComboBox->currentText() );
958  }
959  }
960 
961  if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, QgsGeometry(), &f ) )
962  {
963  int i = mComboBox->findData( f.id(), QgsAttributeTableModel::FeatureIdRole );
964  mComboBox->setCurrentIndex( i );
965  mAddEntryButton->setEnabled( false );
966  }
967 }
968 
969 void QgsRelationReferenceWidget::updateAddEntryButton()
970 {
971  mAddEntryButton->setVisible( mAllowAddFeatures );
972  mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
973 }
974 
975 void QgsRelationReferenceWidget::disableChainedComboBoxes( const QComboBox *scb )
976 {
977  QComboBox *ccb = nullptr;
978  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
979  {
980  if ( !ccb )
981  {
982  if ( cb == scb )
983  {
984  ccb = cb;
985  }
986 
987  continue;
988  }
989 
990  cb->setCurrentIndex( 0 );
991  if ( ccb->currentIndex() == 0 )
992  {
993  cb->setEnabled( false );
994  }
995 
996  ccb = cb;
997  }
998 }
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 setRequest(const QgsFeatureRequest &request)
Set a request that will be used to fill this attribute table model.
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.
A rectangle specified with double values.
Definition: qgsrectangle.h:39
virtual void loadLayer()
Loads the layer into the model Preferably to be called, before using this model as source for any oth...
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.
bool setDisplayExpression(const QString &expression)
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)
Set polygons fill color.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
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:94
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
A model backed by a QgsVectorLayerCache which is able to provide feature/attribute information to a Q...
bool orderByValue()
If the widget will order the combobox entries by value.
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
void setCacheSubsetOfAttributes(const QgsAttributeList &attributes)
Set the subset of attributes to be cached.
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:127
void setForeignKey(const QVariant &value)
this sets the related feature using from the foreign key
virtual void setFilteredFeatures(const QgsFeatureIds &ids)
Specify a list of features, which the filter will accept.
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 setMapTool(QgsMapTool *mapTool)
Sets the map tool currently being used on the canvas.
void setBuffer(double buffer)
Set line / stroke buffer in millimeters.
Definition: qgshighlight.h:87
void setOrderByValue(bool orderByValue)
Set if the widget will order the combobox entries by value.
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
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
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...
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:40
This class wraps a request for features to a vector layer (or directly its vector data provider)...
QgsVectorLayerCache * layerCache() const
Returns the layer cache this model uses as backend.
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.
This class caches features of a given QgsVectorLayer.
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 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.
virtual void sort(int column, Qt::SortOrder order=Qt::AscendingOrder) override
Sort by the given column using the given order.
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.
qint64 QgsFeatureId
Definition: qgsfeature.h:37
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
void setInjectNull(bool injectNull)
If true is specified, a NULL value will be injected.
QList< int > QgsAttributeList
Definition: qgsfield.h:27
bool nextFeature(QgsFeature &f)
Geometry is not required. It may still be returned if e.g. required for a filter condition.
A vector of attributes.
Definition: qgsattributes.h:58
static const double DEFAULT_HIGHLIGHT_BUFFER_MM
Default highlight buffer in mm.
Definition: qgis.h:113
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
QgsFeatureIterator getFeatures(const QgsFeatureRequest &featureRequest=QgsFeatureRequest())
Query this VectorLayerCache for features.
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 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:92