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