QGIS API Documentation  2.99.0-Master (b698612)
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 
202 
203  if ( mEmbedForm )
204  {
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  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  if ( mReadOnlySelector )
304  {
305  QString nullText = QLatin1String( "" );
306  if ( mAllowNull )
307  {
308  nullText = tr( "%1 (no selection)" ).arg( nullValue.toString() );
309  }
310  mLineEdit->setText( nullText );
311  mForeignKey = QVariant();
312  mFeature.setValid( false );
313  }
314  else
315  {
316  if ( mAllowNull )
317  {
318  mComboBox->setCurrentIndex( 0 );
319  }
320  else
321  {
322  mComboBox->setCurrentIndex( -1 );
323  }
324  }
325  mRemoveFKButton->setEnabled( false );
326  updateAttributeEditorFrame( QgsFeature() );
327  emit foreignKeyChanged( QVariant( QVariant::Int ) );
328 }
329 
331 {
332  QgsFeature f;
333  if ( mReferencedLayer )
334  {
335  QgsFeatureId fid;
336  if ( mReadOnlySelector )
337  {
338  fid = mFeature.id();
339  }
340  else
341  {
342  fid = mComboBox->currentData( QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
343  }
344  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( f );
345  }
346  return f;
347 }
348 
350 {
351  if ( mReadOnlySelector )
352  {
353  whileBlocking( mLineEdit )->setText( QString() );
354  }
355  else
356  {
357  whileBlocking( mComboBox )->setCurrentIndex( -1 );
358  }
359  mRemoveFKButton->setEnabled( false );
360  updateAttributeEditorFrame( QgsFeature() );
361 }
362 
364 {
365  if ( mReadOnlySelector )
366  {
367  return mForeignKey;
368  }
369  else
370  {
371  if ( mReferencingFieldIdx < 0 || mReferencingFieldIdx >= mReferencingLayer->fields().count() )
372  {
373  return QVariant();
374  }
375  else if ( !mFeature.isValid() )
376  {
377  return QVariant( mReferencingLayer->fields().at( mReferencingFieldIdx ).type() );
378  }
379  else
380  {
381  return mFeature.attribute( mReferencedFieldIdx );
382  }
383  }
384 }
385 
387 {
388  mEditorContext = context;
389  mCanvas = canvas;
390  mMessageBar = messageBar;
391 
392  if ( mMapTool )
393  delete mMapTool;
394  mMapTool = new QgsMapToolIdentifyFeature( mCanvas );
395  mMapTool->setButton( mMapIdentificationButton );
396 }
397 
399 {
400  if ( display )
401  {
402  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
403  mTopLayout->setAlignment( Qt::AlignTop );
404  }
405 
406  mAttributeEditorFrame->setVisible( display );
407  mEmbedForm = display;
408 }
409 
411 {
412  mChooserContainer->setHidden( readOnly );
413  mLineEdit->setVisible( readOnly );
414  mRemoveFKButton->setVisible( mAllowNull && readOnly );
415  mReadOnlySelector = readOnly;
416 }
417 
419 {
420  mHighlightFeatureButton->setVisible( allowMapIdentification );
421  mMapIdentificationButton->setVisible( allowMapIdentification );
422  mAllowMapIdentification = allowMapIdentification;
423 }
424 
426 {
427  mOrderByValue = orderByValue;
428 }
429 
430 void QgsRelationReferenceWidget::setFilterFields( const QStringList &filterFields )
431 {
432  mFilterFields = filterFields;
433 }
434 
436 {
437  mOpenFormButton->setVisible( openFormButtonVisible );
438  mOpenFormButtonVisible = openFormButtonVisible;
439 }
440 
442 {
443  mChainFilters = chainFilters;
444 }
445 
447 {
448  Q_UNUSED( e )
449 
450  mShown = true;
451 
452  init();
453 }
454 
456 {
457  if ( !mReadOnlySelector && mComboBox->count() == 0 && mReferencedLayer )
458  {
459  QApplication::setOverrideCursor( Qt::WaitCursor );
460 
461  QSet<QString> requestedAttrs;
462 
463  QgsVectorLayerCache *layerCache = new QgsVectorLayerCache( mReferencedLayer, 100000, this );
464 
465  if ( !mFilterFields.isEmpty() )
466  {
467  Q_FOREACH ( const QString &fieldName, mFilterFields )
468  {
469  int idx = mReferencedLayer->fields().lookupField( fieldName );
470  QComboBox *cb = new QComboBox();
471  cb->setProperty( "Field", fieldName );
472  cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
473  mFilterComboBoxes << cb;
474  QVariantList uniqueValues = mReferencedLayer->uniqueValues( idx ).toList();
475  cb->addItem( mReferencedLayer->attributeDisplayName( idx ) );
476  QVariant nullValue = QgsApplication::nullRepresentation();
477  cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->fields().at( idx ).type() ) );
478 
479  std::sort( uniqueValues.begin(), uniqueValues.end(), qgsVariantLessThan );
480  Q_FOREACH ( const QVariant &v, uniqueValues )
481  {
482  cb->addItem( v.toString(), v );
483  }
484 
485  connect( cb, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::filterChanged );
486 
487  // Request this attribute for caching
488  requestedAttrs << fieldName;
489 
490  mFilterLayout->addWidget( cb );
491  }
492 
493  if ( mChainFilters )
494  {
495  QVariant nullValue = QgsApplication::nullRepresentation();
496 
497  QgsFeature ft;
498  QgsFeatureIterator fit = layerCache->getFeatures();
499  while ( fit.nextFeature( ft ) )
500  {
501  for ( int i = 0; i < mFilterComboBoxes.count() - 1; ++i )
502  {
503  QVariant cv = ft.attribute( mFilterFields.at( i ) );
504  QVariant nv = ft.attribute( mFilterFields.at( i + 1 ) );
505  QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
506  QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
507  mFilterCache[mFilterFields[i]][cf] << nf;
508  }
509  }
510  }
511  }
512  else
513  {
514  mFilterContainer->hide();
515  }
516 
517  QgsExpression displayExpression( mReferencedLayer->displayExpression() );
518 
519  requestedAttrs += displayExpression.referencedColumns();
520  requestedAttrs << mRelation.fieldPairs().at( 0 ).second;
521 
522  Q_FOREACH ( const QgsConditionalStyle &style, mReferencedLayer->conditionalStyles()->rowStyles() )
523  {
524  QgsExpression exp( style.rule() );
525  requestedAttrs += exp.referencedColumns();
526  }
527 
528  if ( displayExpression.isField() )
529  {
530  Q_FOREACH ( const QgsConditionalStyle &style, mReferencedLayer->conditionalStyles()->fieldStyles( *displayExpression.referencedColumns().constBegin() ) )
531  {
532  QgsExpression exp( style.rule() );
533  requestedAttrs += exp.referencedColumns();
534  }
535  }
536 
537  QgsAttributeList attributes;
538  Q_FOREACH ( const QString &attr, requestedAttrs )
539  attributes << mReferencedLayer->fields().lookupField( attr );
540 
541  layerCache->setCacheSubsetOfAttributes( attributes );
542  mMasterModel = new QgsAttributeTableModel( layerCache );
543  mMasterModel->setRequest( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( requestedAttrs, mReferencedLayer->fields() ) );
544  mFilterModel = new QgsAttributeTableFilterModel( mCanvas, mMasterModel, mMasterModel );
545  mFeatureListModel = new QgsFeatureListModel( mFilterModel, this );
546  mFeatureListModel->setDisplayExpression( mReferencedLayer->displayExpression() );
547 
548  mMasterModel->loadLayer();
549 
550  mFeatureListModel->setInjectNull( mAllowNull );
551  if ( mOrderByValue )
552  {
553  mFilterModel->sort( mReferencedLayer->displayExpression() );
554  }
555 
556  mComboBox->setModel( mFeatureListModel );
557 
558  delete mComboBox->completer();
559  QCompleter *completer = new QCompleter( mComboBox->model(), mComboBox );
560  completer->setModel( mComboBox->model() );
561  completer->setFilterMode( Qt::MatchContains );
562  completer->setCaseSensitivity( Qt::CaseInsensitive );
563  mComboBox->setCompleter( completer );
564 
565 
566  QVariant nullValue = QgsApplication::nullRepresentation();
567 
568  if ( mChainFilters && mFeature.isValid() )
569  {
570  for ( int i = 0; i < mFilterFields.size(); i++ )
571  {
572  QVariant v = mFeature.attribute( mFilterFields[i] );
573  QString f = v.isNull() ? nullValue.toString() : v.toString();
574  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
575  }
576  }
577 
578  QVariant featId = mFeature.isValid() ? mFeature.id() : QVariant( QVariant::Int );
579  mComboBox->setCurrentIndex( mComboBox->findData( featId, QgsAttributeTableModel::FeatureIdRole ) );
580 
581  // Only connect after iterating, to have only one iterator on the referenced table at once
582  connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::comboReferenceChanged );
583  updateAttributeEditorFrame( mFeature );
584  QApplication::restoreOverrideCursor();
585  }
586 }
587 
588 void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
589 {
590  if ( action == mHighlightFeatureAction )
591  {
592  highlightFeature();
593  }
594  else if ( action == mScaleHighlightFeatureAction )
595  {
596  highlightFeature( QgsFeature(), Scale );
597  }
598  else if ( action == mPanHighlightFeatureAction )
599  {
600  highlightFeature( QgsFeature(), Pan );
601  }
602 }
603 
605 {
606  QgsFeature feat = referencedFeature();
607 
608  if ( !feat.isValid() )
609  return;
610 
612  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
613  attributeDialog.exec();
614 }
615 
616 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
617 {
618  if ( !mCanvas )
619  return;
620 
621  if ( !f.isValid() )
622  {
623  f = referencedFeature();
624  if ( !f.isValid() )
625  return;
626  }
627 
628  if ( !f.hasGeometry() )
629  {
630  return;
631  }
632 
633  QgsGeometry geom = f.geometry();
634 
635  // scale or pan
636  if ( canvasExtent == Scale )
637  {
638  QgsRectangle featBBox = geom.boundingBox();
639  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
640  QgsRectangle extent = mCanvas->extent();
641  if ( !extent.contains( featBBox ) )
642  {
643  extent.combineExtentWith( featBBox );
644  extent.scale( 1.1 );
645  mCanvas->setExtent( extent );
646  mCanvas->refresh();
647  }
648  }
649  else if ( canvasExtent == Pan )
650  {
651  QgsGeometry centroid = geom.centroid();
652  QgsPointXY center = centroid.asPoint();
653  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
654  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
655  }
656 
657  // highlight
658  deleteHighlight();
659  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
660  QgsSettings settings;
661  QColor color = QColor( settings.value( QStringLiteral( "Map/highlight/color" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
662  int alpha = settings.value( QStringLiteral( "Map/highlight/colorAlpha" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
663  double buffer = settings.value( QStringLiteral( "Map/highlight/buffer" ), Qgis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
664  double minWidth = settings.value( QStringLiteral( "Map/highlight/minWidth" ), Qgis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
665 
666  mHighlight->setColor( color ); // sets also fill with default alpha
667  color.setAlpha( alpha );
668  mHighlight->setFillColor( color ); // sets fill with alpha
669  mHighlight->setBuffer( buffer );
670  mHighlight->setMinWidth( minWidth );
671  mHighlight->show();
672 
673  QTimer *timer = new QTimer( this );
674  timer->setSingleShot( true );
675  connect( timer, &QTimer::timeout, this, &QgsRelationReferenceWidget::deleteHighlight );
676  timer->start( 3000 );
677 }
678 
679 void QgsRelationReferenceWidget::deleteHighlight()
680 {
681  if ( mHighlight )
682  {
683  mHighlight->hide();
684  delete mHighlight;
685  }
686  mHighlight = nullptr;
687 }
688 
690 {
691  if ( !mAllowMapIdentification || !mReferencedLayer )
692  return;
693 
694  const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
695  if ( !tools )
696  return;
697  if ( !mCanvas )
698  return;
699 
700  mMapTool->setLayer( mReferencedLayer );
701  mCanvas->setMapTool( mMapTool );
702 
703  mWindowWidget = window();
704 
705  mCanvas->window()->raise();
706  mCanvas->activateWindow();
707  mCanvas->setFocus();
708 
709  connect( mMapTool, static_cast<void ( QgsMapToolIdentifyFeature::* )( const QgsFeature & )>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
710  connect( mMapTool, &QgsMapTool::deactivated, this, &QgsRelationReferenceWidget::mapToolDeactivated );
711 
712  if ( mMessageBar )
713  {
714  QString title = tr( "Relation %1 for %2." ).arg( mRelationName, mReferencingLayer->name() );
715  QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
716  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
717  mMessageBar->pushItem( mMessageBarItem );
718  }
719 }
720 
721 void QgsRelationReferenceWidget::comboReferenceChanged( int index )
722 {
723  QgsFeatureId fid = mComboBox->itemData( index, QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
724  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( mFeature );
725  highlightFeature( mFeature );
726  updateAttributeEditorFrame( mFeature );
727  emit foreignKeyChanged( mFeature.attribute( mReferencedFieldIdx ) );
728 }
729 
730 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature &feature )
731 {
732  mOpenFormButton->setEnabled( feature.isValid() );
733  // Check if we're running with an embedded frame we need to update
734  if ( mAttributeEditorFrame && mReferencedAttributeForm )
735  {
736  mReferencedAttributeForm->setFeature( feature );
737  }
738 }
739 
741 {
742  return mAllowAddFeatures;
743 }
744 
746 {
747  mAllowAddFeatures = allowAddFeatures;
748  updateAddEntryButton();
749 }
750 
751 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature &feature )
752 {
753  if ( mReadOnlySelector )
754  {
755  QgsExpression expr( mReferencedLayer->displayExpression() );
757  context.setFeature( feature );
758  QString title = expr.evaluate( &context ).toString();
759  if ( expr.hasEvalError() )
760  {
761  title = feature.attribute( mReferencedFieldIdx ).toString();
762  }
763  mLineEdit->setText( title );
764  mForeignKey = feature.attribute( mReferencedFieldIdx );
765  mFeature = feature;
766  }
767  else
768  {
769  mComboBox->setCurrentIndex( mComboBox->findData( feature.id(), QgsAttributeTableModel::FeatureIdRole ) );
770  mFeature = feature;
771  }
772 
773  mRemoveFKButton->setEnabled( mIsEditable );
774  highlightFeature( feature );
775  updateAttributeEditorFrame( feature );
776  emit foreignKeyChanged( foreignKey() );
777 
778  unsetMapTool();
779 }
780 
781 void QgsRelationReferenceWidget::unsetMapTool()
782 {
783  // deactivate map tool if activated
784  if ( mCanvas && mMapTool )
785  {
786  /* this will call mapToolDeactivated */
787  mCanvas->unsetMapTool( mMapTool );
788  }
789 }
790 
791 void QgsRelationReferenceWidget::mapToolDeactivated()
792 {
793  if ( mWindowWidget )
794  {
795  mWindowWidget->raise();
796  mWindowWidget->activateWindow();
797  }
798 
799  if ( mMessageBar && mMessageBarItem )
800  {
801  mMessageBar->popWidget( mMessageBarItem );
802  }
803  mMessageBarItem = nullptr;
804 }
805 
806 void QgsRelationReferenceWidget::filterChanged()
807 {
808  QVariant nullValue = QgsApplication::nullRepresentation();
809 
810  QStringList filters;
811  QgsAttributeList attrs;
812 
813  QComboBox *scb = qobject_cast<QComboBox *>( sender() );
814 
815  Q_ASSERT( scb );
816 
817  if ( mChainFilters )
818  {
819  QComboBox *ccb = nullptr;
820  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
821  {
822  if ( !ccb )
823  {
824  if ( cb == scb )
825  ccb = cb;
826 
827  continue;
828  }
829 
830  if ( ccb->currentIndex() == 0 )
831  {
832  cb->setCurrentIndex( 0 );
833  cb->setEnabled( false );
834  }
835  else
836  {
837  cb->blockSignals( true );
838  cb->clear();
839  cb->addItem( cb->property( "FieldAlias" ).toString() );
840 
841  // ccb = scb
842  // cb = scb + 1
843  QStringList texts;
844  Q_FOREACH ( const QString &txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
845  {
846  texts << txt;
847  }
848  texts.sort();
849  cb->addItems( texts );
850 
851  cb->setEnabled( true );
852  cb->blockSignals( false );
853 
854  ccb = cb;
855  }
856  }
857  }
858 
859  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
860  {
861  if ( cb->currentIndex() != 0 )
862  {
863  const QString fieldName = cb->property( "Field" ).toString();
864 
865  if ( cb->currentText() == nullValue.toString() )
866  {
867  filters << QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
868  }
869  else
870  {
871  if ( mReferencedLayer->fields().field( fieldName ).type() == QVariant::String )
872  {
873  filters << QStringLiteral( "\"%1\" = '%2'" ).arg( fieldName, cb->currentText() );
874  }
875  else
876  {
877  filters << QStringLiteral( "\"%1\" = %2" ).arg( fieldName, cb->currentText() );
878  }
879  }
880  attrs << mReferencedLayer->fields().lookupField( fieldName );
881  }
882  }
883 
884  QString filterExpression = filters.join( QStringLiteral( " AND " ) );
885 
886  QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( filterExpression ).setSubsetOfAttributes( attrs ) ) );
887 
888  QgsFeature f;
889  QgsFeatureIds featureIds;
890 
891  while ( it.nextFeature( f ) )
892  {
893  featureIds << f.id();
894  }
895 
896  mFilterModel->setFilteredFeatures( featureIds );
897 }
898 
899 void QgsRelationReferenceWidget::addEntry()
900 {
901  QgsFeature f( mReferencedLayer->fields() );
902  QgsAttributeMap attributes;
903 
904  // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
905  if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
906  {
907  int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
908 
909  if ( fieldIdx != -1 )
910  {
911  attributes.insert( fieldIdx, mComboBox->currentText() );
912  }
913  }
914 
915  if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, QgsGeometry(), &f ) )
916  {
917  int i = mComboBox->findData( f.id(), QgsAttributeTableModel::FeatureIdRole );
918  mComboBox->setCurrentIndex( i );
919  mAddEntryButton->setEnabled( false );
920  }
921 }
922 
923 void QgsRelationReferenceWidget::updateAddEntryButton()
924 {
925  mAddEntryButton->setVisible( mAllowAddFeatures );
926  mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
927 }
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.
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:136
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 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:180
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
Return 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.
QgsField field(int fieldIdx) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:140
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