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