QGIS API Documentation  2.99.0-Master (8ec3eaf)
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 
23 #include "qgsattributeform.h"
25 #include "qgsattributedialog.h"
26 #include "qgsapplication.h"
27 #include "qgscollapsiblegroupbox.h"
28 #include "qgseditorwidgetfactory.h"
29 #include "qgsexpression.h"
30 #include "qgsfeaturelistmodel.h"
31 #include "qgsfields.h"
32 #include "qgsgeometry.h"
33 #include "qgshighlight.h"
34 #include "qgsmapcanvas.h"
35 #include "qgsmessagebar.h"
37 #include "qgsvectorlayer.h"
38 #include "qgsattributetablemodel.h"
40 #include "qgsfeatureiterator.h"
41 
43  : QWidget( parent )
44  , mEditorContext( QgsAttributeEditorContext() )
45  , mCanvas( nullptr )
46  , mMessageBar( nullptr )
47  , mForeignKey( QVariant() )
48  , mReferencedFieldIdx( -1 )
49  , mReferencingFieldIdx( -1 )
50  , mAllowNull( true )
51  , mHighlight( nullptr )
52  , mMapTool( nullptr )
53  , mMessageBarItem( nullptr )
54  , mRelationName( QLatin1String( "" ) )
55  , mReferencedAttributeForm( nullptr )
56  , mReferencedLayer( nullptr )
57  , mReferencingLayer( nullptr )
58  , mMasterModel( nullptr )
59  , mFilterModel( nullptr )
60  , mFeatureListModel( nullptr )
61  , mWindowWidget( nullptr )
62  , mShown( false )
63  , mIsEditable( true )
64  , mEmbedForm( false )
65  , mReadOnlySelector( false )
66  , mAllowMapIdentification( false )
67  , mOrderByValue( false )
68  , mOpenFormButtonVisible( true )
69  , mChainFilters( false )
70  , mAllowAddFeatures( false )
71 {
72  mTopLayout = new QVBoxLayout( this );
73  mTopLayout->setContentsMargins( 0, 0, 0, 0 );
74 
75  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::Fixed );
76 
77  setLayout( mTopLayout );
78 
79  QHBoxLayout* editLayout = new QHBoxLayout();
80  editLayout->setContentsMargins( 0, 0, 0, 0 );
81  editLayout->setSpacing( 2 );
82 
83  // Prepare the container and layout for the filter comboboxes
84  mChooserContainer = new QWidget;
85  editLayout->addWidget( mChooserContainer );
86  QHBoxLayout* chooserLayout = new QHBoxLayout;
87  chooserLayout->setContentsMargins( 0, 0, 0, 0 );
88  mFilterLayout = new QHBoxLayout;
89  mFilterLayout->setContentsMargins( 0, 0, 0, 0 );
90  mFilterContainer = new QWidget;
91  mFilterContainer->setLayout( mFilterLayout );
92  mChooserContainer->setLayout( chooserLayout );
93  chooserLayout->addWidget( mFilterContainer );
94 
95  // combobox (for non-geometric relation)
96  mComboBox = new QComboBox();
97  mChooserContainer->layout()->addWidget( mComboBox );
98 
99  // read-only line edit
100  mLineEdit = new QLineEdit();
101  mLineEdit->setReadOnly( true );
102  editLayout->addWidget( mLineEdit );
103 
104  // open form button
105  mOpenFormButton = new QToolButton();
106  mOpenFormButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPropertyItem.svg" ) ) );
107  mOpenFormButton->setText( tr( "Open related feature form" ) );
108  editLayout->addWidget( mOpenFormButton );
109 
110  mAddEntryButton = new QToolButton();
111  mAddEntryButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAdd.svg" ) ) );
112  mAddEntryButton->setText( tr( "Add new entry" ) );
113  editLayout->addWidget( mAddEntryButton );
114 
115  // highlight button
116  mHighlightFeatureButton = new QToolButton( this );
117  mHighlightFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
118  mHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHighlightFeature.svg" ) ), tr( "Highlight feature" ), this );
119  mScaleHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleHighlightFeature.svg" ) ), tr( "Scale and highlight feature" ), this );
120  mPanHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPanHighlightFeature.svg" ) ), tr( "Pan and highlight feature" ), this );
121  mHighlightFeatureButton->addAction( mHighlightFeatureAction );
122  mHighlightFeatureButton->addAction( mScaleHighlightFeatureAction );
123  mHighlightFeatureButton->addAction( mPanHighlightFeatureAction );
124  mHighlightFeatureButton->setDefaultAction( mHighlightFeatureAction );
125  editLayout->addWidget( mHighlightFeatureButton );
126 
127  // map identification button
128  mMapIdentificationButton = new QToolButton( this );
129  mMapIdentificationButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMapIdentification.svg" ) ) );
130  mMapIdentificationButton->setText( tr( "Select on map" ) );
131  mMapIdentificationButton->setCheckable( true );
132  editLayout->addWidget( mMapIdentificationButton );
133 
134  // remove foreign key button
135  mRemoveFKButton = new QToolButton( this );
136  mRemoveFKButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemove.svg" ) ) );
137  mRemoveFKButton->setText( tr( "No selection" ) );
138  editLayout->addWidget( mRemoveFKButton );
139 
140  // add line to top layout
141  mTopLayout->addLayout( editLayout );
142 
143  // embed form
144  mAttributeEditorFrame = new QgsCollapsibleGroupBox( this );
145  mAttributeEditorLayout = new QVBoxLayout( mAttributeEditorFrame );
146  mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
147  mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
148  mTopLayout->addWidget( mAttributeEditorFrame );
149 
150  // invalid label
151  mInvalidLabel = new QLabel( tr( "The relation is not valid. Please make sure your relation definitions are ok." ) );
152  mInvalidLabel->setWordWrap( true );
153  QFont font = mInvalidLabel->font();
154  font.setItalic( true );
155  mInvalidLabel->setStyleSheet( QStringLiteral( "QLabel { color: red; } " ) );
156  mInvalidLabel->setFont( font );
157  mTopLayout->addWidget( mInvalidLabel );
158 
159  // default mode is combobox, no geometric relation and no embed form
160  mLineEdit->hide();
161  mMapIdentificationButton->hide();
162  mHighlightFeatureButton->hide();
163  mAttributeEditorFrame->hide();
164  mInvalidLabel->hide();
165 
166  // connect buttons
167  connect( mOpenFormButton, SIGNAL( clicked() ), this, SLOT( openForm() ) );
168  connect( mHighlightFeatureButton, SIGNAL( triggered( QAction* ) ), this, SLOT( highlightActionTriggered( QAction* ) ) );
169  connect( mMapIdentificationButton, SIGNAL( clicked() ), this, SLOT( mapIdentification() ) );
170  connect( mRemoveFKButton, SIGNAL( clicked() ), this, SLOT( deleteForeignKey() ) );
171  connect( mAddEntryButton, SIGNAL( clicked( bool ) ), this, SLOT( addEntry() ) );
172  connect( mComboBox, SIGNAL( editTextChanged( QString ) ), this, SLOT( updateAddEntryButton() ) );
173 }
174 
176 {
177  deleteHighlight();
178  unsetMapTool();
179  if ( mMapTool )
180  delete mMapTool;
181 }
182 
183 void QgsRelationReferenceWidget::setRelation( const QgsRelation& relation, bool allowNullValue )
184 {
185  mAllowNull = allowNullValue;
186  mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
187 
188  if ( relation.isValid() )
189  {
190  mInvalidLabel->hide();
191 
192  mRelation = relation;
193  mReferencingLayer = relation.referencingLayer();
194  mRelationName = relation.name();
195  mReferencedLayer = relation.referencedLayer();
196  mReferencedFieldIdx = mReferencedLayer->fields().lookupField( relation.fieldPairs().at( 0 ).second );
197  mReferencingFieldIdx = mReferencingLayer->fields().lookupField( relation.fieldPairs().at( 0 ).first );
198  mAttributeEditorFrame->setObjectName( QStringLiteral( "referencing/" ) + relation.name() );
199 
201 
202  if ( mEmbedForm )
203  {
204  mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
205  mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
206  mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
207  }
208 
209  connect( mReferencedLayer, SIGNAL( editingStarted() ), this, SLOT( updateAddEntryButton() ) );
210  connect( mReferencedLayer, SIGNAL( editingStopped() ), this, SLOT( updateAddEntryButton() ) );
211  updateAddEntryButton();
212  }
213  else
214  {
215  mInvalidLabel->show();
216  }
217 
218  if ( mShown && isVisible() )
219  {
220  init();
221  }
222 }
223 
225 {
226  if ( !editable )
227  unsetMapTool();
228 
229  mFilterContainer->setEnabled( editable );
230  mComboBox->setEnabled( editable );
231  mComboBox->setEditable( true );
232  mMapIdentificationButton->setEnabled( editable );
233  mRemoveFKButton->setEnabled( editable );
234  mIsEditable = editable;
235 }
236 
237 void QgsRelationReferenceWidget::setForeignKey( const QVariant& value )
238 {
239  if ( !value.isValid() )
240  {
241  return;
242  }
243  if ( value.isNull() )
244  {
246  return;
247  }
248 
249  if ( !mReferencedLayer )
250  return;
251 
252  // Attributes from the referencing layer
253  QgsAttributes attrs = QgsAttributes( mReferencingLayer->fields().count() );
254  // Set the value on the foreign key field of the referencing record
255  attrs[ mReferencingLayer->fields().lookupField( mRelation.fieldPairs().at( 0 ).first )] = value;
256 
257  QgsFeatureRequest request = mRelation.getReferencedFeatureRequest( attrs );
258 
259  mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
260 
261  if ( !mFeature.isValid() )
262  {
263  return;
264  }
265 
266  mForeignKey = mFeature.attribute( mReferencedFieldIdx );
267 
268  if ( mReadOnlySelector )
269  {
270  QgsExpression expr( mReferencedLayer->displayExpression() );
271  QgsExpressionContext context;
274  << QgsExpressionContextUtils::layerScope( mReferencedLayer );
275  context.setFeature( mFeature );
276  QString title = expr.evaluate( &context ).toString();
277  if ( expr.hasEvalError() )
278  {
279  title = mFeature.attribute( mReferencedFieldIdx ).toString();
280  }
281  mLineEdit->setText( title );
282  }
283  else
284  {
285  int i = mComboBox->findData( mFeature.id(), QgsAttributeTableModel::FeatureIdRole );
286  if ( i == -1 && mAllowNull )
287  {
288  mComboBox->setCurrentIndex( 0 );
289  }
290  else
291  {
292  mComboBox->setCurrentIndex( i );
293  }
294  }
295 
296  mRemoveFKButton->setEnabled( mIsEditable );
297  highlightFeature( mFeature );
298  updateAttributeEditorFrame( mFeature );
299  emit foreignKeyChanged( foreignKey() );
300 }
301 
303 {
304  QVariant nullValue = QSettings().value( QStringLiteral( "qgis/nullValue" ), "NULL" );
305  if ( mReadOnlySelector )
306  {
307  QString nullText = QLatin1String( "" );
308  if ( mAllowNull )
309  {
310  nullText = tr( "%1 (no selection)" ).arg( nullValue.toString() );
311  }
312  mLineEdit->setText( nullText );
313  mForeignKey = QVariant();
314  mFeature.setValid( false );
315  }
316  else
317  {
318  if ( mAllowNull )
319  {
320  mComboBox->setCurrentIndex( 0 );
321  }
322  else
323  {
324  mComboBox->setCurrentIndex( -1 );
325  }
326  }
327  mRemoveFKButton->setEnabled( false );
328  updateAttributeEditorFrame( QgsFeature() );
329  emit foreignKeyChanged( QVariant( QVariant::Int ) );
330 }
331 
333 {
334  QgsFeature f;
335  if ( mReferencedLayer )
336  {
337  QgsFeatureId fid;
338  if ( mReadOnlySelector )
339  {
340  fid = mFeature.id();
341  }
342  else
343  {
344  fid = mComboBox->itemData( mComboBox->currentIndex(), QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
345  }
346  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( f );
347  }
348  return f;
349 }
350 
352 {
353  if ( mReadOnlySelector )
354  {
355  whileBlocking( mLineEdit )->setText( QString() );
356  }
357  else
358  {
359  whileBlocking( mComboBox )->setCurrentIndex( -1 );
360  }
361  mRemoveFKButton->setEnabled( false );
362  updateAttributeEditorFrame( QgsFeature() );
363 }
364 
366 {
367  if ( mReadOnlySelector )
368  {
369  return mForeignKey;
370  }
371  else
372  {
373  if ( mReferencingFieldIdx < 0 || mReferencingFieldIdx >= mReferencingLayer->fields().count() )
374  {
375  return QVariant();
376  }
377  else if ( !mFeature.isValid() )
378  {
379  return QVariant( mReferencingLayer->fields().at( mReferencingFieldIdx ).type() );
380  }
381  else
382  {
383  return mFeature.attribute( mReferencedFieldIdx );
384  }
385  }
386 }
387 
389 {
390  mEditorContext = context;
391  mCanvas = canvas;
392  mMessageBar = messageBar;
393 
394  if ( mMapTool )
395  delete mMapTool;
396  mMapTool = new QgsMapToolIdentifyFeature( mCanvas );
397  mMapTool->setButton( mMapIdentificationButton );
398 }
399 
401 {
402  if ( display )
403  {
404  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
405  mTopLayout->setAlignment( Qt::AlignTop );
406  }
407 
408  mAttributeEditorFrame->setVisible( display );
409  mEmbedForm = display;
410 }
411 
413 {
414  mChooserContainer->setHidden( readOnly );
415  mLineEdit->setVisible( readOnly );
416  mRemoveFKButton->setVisible( mAllowNull && readOnly );
417  mReadOnlySelector = readOnly;
418 }
419 
421 {
422  mHighlightFeatureButton->setVisible( allowMapIdentification );
423  mMapIdentificationButton->setVisible( allowMapIdentification );
424  mAllowMapIdentification = allowMapIdentification;
425 }
426 
428 {
429  mOrderByValue = orderByValue;
430 }
431 
432 void QgsRelationReferenceWidget::setFilterFields( const QStringList& filterFields )
433 {
434  mFilterFields = filterFields;
435 }
436 
438 {
439  mOpenFormButton->setVisible( openFormButtonVisible );
440  mOpenFormButtonVisible = openFormButtonVisible;
441 }
442 
444 {
445  mChainFilters = chainFilters;
446 }
447 
449 {
450  Q_UNUSED( e )
451 
452  mShown = true;
453 
454  init();
455 }
456 
458 {
459  if ( !mReadOnlySelector && mComboBox->count() == 0 && mReferencedLayer )
460  {
461  QApplication::setOverrideCursor( Qt::WaitCursor );
462 
463  QSet<QString> requestedAttrs;
464 
465  QgsVectorLayerCache* layerCache = new QgsVectorLayerCache( mReferencedLayer, 100000, this );
466 
467  if ( !mFilterFields.isEmpty() )
468  {
469  Q_FOREACH ( const QString& fieldName, mFilterFields )
470  {
471  QVariantList uniqueValues;
472  int idx = mReferencedLayer->fields().lookupField( fieldName );
473  QComboBox* cb = new QComboBox();
474  cb->setProperty( "Field", fieldName );
475  cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
476  mFilterComboBoxes << cb;
477  mReferencedLayer->uniqueValues( idx, uniqueValues );
478  cb->addItem( mReferencedLayer->attributeDisplayName( idx ) );
479  QVariant nullValue = QSettings().value( QStringLiteral( "qgis/nullValue" ), "NULL" );
480  cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->fields().at( idx ).type() ) );
481 
482  qSort( uniqueValues.begin(), uniqueValues.end(), qgsVariantLessThan );
483  Q_FOREACH ( const QVariant& v, uniqueValues )
484  {
485  cb->addItem( v.toString(), v );
486  }
487 
488  connect( cb, SIGNAL( currentIndexChanged( int ) ), this, SLOT( filterChanged() ) );
489 
490  // Request this attribute for caching
491  requestedAttrs << fieldName;
492 
493  mFilterLayout->addWidget( cb );
494  }
495 
496  if ( mChainFilters )
497  {
498  QVariant nullValue = QSettings().value( QStringLiteral( "qgis/nullValue" ), "NULL" );
499 
500  QgsFeature ft;
501  QgsFeatureIterator fit = layerCache->getFeatures();
502  while ( fit.nextFeature( ft ) )
503  {
504  for ( int i = 0; i < mFilterComboBoxes.count() - 1; ++i )
505  {
506  QVariant cv = ft.attribute( mFilterFields.at( i ) );
507  QVariant nv = ft.attribute( mFilterFields.at( i + 1 ) );
508  QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
509  QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
510  mFilterCache[mFilterFields[i]][cf] << nf;
511  }
512  }
513  }
514  }
515  else
516  {
517  mFilterContainer->hide();
518  }
519 
520  QgsExpression exp( mReferencedLayer->displayExpression() );
521 
522  requestedAttrs += exp.referencedColumns();
523  requestedAttrs << mRelation.fieldPairs().at( 0 ).second;
524 
525  QgsAttributeList attributes;
526  Q_FOREACH ( const QString& attr, requestedAttrs )
527  attributes << mReferencedLayer->fields().lookupField( attr );
528 
529  layerCache->setCacheSubsetOfAttributes( attributes );
530  mMasterModel = new QgsAttributeTableModel( layerCache );
531  mMasterModel->setRequest( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( requestedAttrs, mReferencedLayer->fields() ) );
532  mFilterModel = new QgsAttributeTableFilterModel( mCanvas, mMasterModel, mMasterModel );
533  mFeatureListModel = new QgsFeatureListModel( mFilterModel, this );
534  mFeatureListModel->setDisplayExpression( mReferencedLayer->displayExpression() );
535 
536  mMasterModel->loadLayer();
537 
538  mFeatureListModel->setInjectNull( mAllowNull );
539  if ( mOrderByValue )
540  {
541  mFilterModel->sort( mReferencedLayer->displayExpression() );
542  }
543 
544  mComboBox->setModel( mFeatureListModel );
545 
546  QVariant nullValue = QSettings().value( QStringLiteral( "qgis/nullValue" ), "NULL" );
547 
548  if ( mChainFilters && mFeature.isValid() )
549  {
550  for ( int i = 0; i < mFilterFields.size(); i++ )
551  {
552  QVariant v = mFeature.attribute( mFilterFields[i] );
553  QString f = v.isNull() ? nullValue.toString() : v.toString();
554  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
555  }
556  }
557 
558  QVariant featId = mFeature.isValid() ? mFeature.id() : QVariant( QVariant::Int );
559  mComboBox->setCurrentIndex( mComboBox->findData( featId, QgsAttributeTableModel::FeatureIdRole ) );
560 
561  // Only connect after iterating, to have only one iterator on the referenced table at once
562  connect( mComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( comboReferenceChanged( int ) ) );
563  updateAttributeEditorFrame( mFeature );
564  QApplication::restoreOverrideCursor();
565  }
566 }
567 
568 void QgsRelationReferenceWidget::highlightActionTriggered( QAction* action )
569 {
570  if ( action == mHighlightFeatureAction )
571  {
572  highlightFeature();
573  }
574  else if ( action == mScaleHighlightFeatureAction )
575  {
576  highlightFeature( QgsFeature(), Scale );
577  }
578  else if ( action == mPanHighlightFeatureAction )
579  {
580  highlightFeature( QgsFeature(), Pan );
581  }
582 }
583 
585 {
586  QgsFeature feat = referencedFeature();
587 
588  if ( !feat.isValid() )
589  return;
590 
592  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
593  attributeDialog.exec();
594 }
595 
596 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
597 {
598  if ( !mCanvas )
599  return;
600 
601  if ( !f.isValid() )
602  {
603  f = referencedFeature();
604  if ( !f.isValid() )
605  return;
606  }
607 
608  if ( !f.hasGeometry() )
609  {
610  return;
611  }
612 
613  QgsGeometry geom = f.geometry();
614 
615  // scale or pan
616  if ( canvasExtent == Scale )
617  {
618  QgsRectangle featBBox = geom.boundingBox();
619  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
620  QgsRectangle extent = mCanvas->extent();
621  if ( !extent.contains( featBBox ) )
622  {
623  extent.combineExtentWith( featBBox );
624  extent.scale( 1.1 );
625  mCanvas->setExtent( extent );
626  mCanvas->refresh();
627  }
628  }
629  else if ( canvasExtent == Pan )
630  {
631  QgsGeometry centroid = geom.centroid();
632  QgsPoint center = centroid.asPoint();
633  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
634  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
635  }
636 
637  // highlight
638  deleteHighlight();
639  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
640  QSettings settings;
641  QColor color = QColor( settings.value( QStringLiteral( "/Map/highlight/color" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
642  int alpha = settings.value( QStringLiteral( "/Map/highlight/colorAlpha" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
643  double buffer = settings.value( QStringLiteral( "/Map/highlight/buffer" ), Qgis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
644  double minWidth = settings.value( QStringLiteral( "/Map/highlight/minWidth" ), Qgis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
645 
646  mHighlight->setColor( color ); // sets also fill with default alpha
647  color.setAlpha( alpha );
648  mHighlight->setFillColor( color ); // sets fill with alpha
649  mHighlight->setBuffer( buffer );
650  mHighlight->setMinWidth( minWidth );
651  mHighlight->show();
652 
653  QTimer* timer = new QTimer( this );
654  timer->setSingleShot( true );
655  connect( timer, SIGNAL( timeout() ), this, SLOT( deleteHighlight() ) );
656  timer->start( 3000 );
657 }
658 
659 void QgsRelationReferenceWidget::deleteHighlight()
660 {
661  if ( mHighlight )
662  {
663  mHighlight->hide();
664  delete mHighlight;
665  }
666  mHighlight = nullptr;
667 }
668 
670 {
671  if ( !mAllowMapIdentification || !mReferencedLayer )
672  return;
673 
674  const QgsVectorLayerTools* tools = mEditorContext.vectorLayerTools();
675  if ( !tools )
676  return;
677  if ( !mCanvas )
678  return;
679 
680  mMapTool->setLayer( mReferencedLayer );
681  mCanvas->setMapTool( mMapTool );
682 
683  mWindowWidget = window();
684 
685  mCanvas->window()->raise();
686  mCanvas->activateWindow();
687  mCanvas->setFocus();
688 
689  connect( mMapTool, SIGNAL( featureIdentified( QgsFeature ) ), this, SLOT( featureIdentified( const QgsFeature ) ) );
690  connect( mMapTool, SIGNAL( deactivated() ), this, SLOT( mapToolDeactivated() ) );
691 
692  if ( mMessageBar )
693  {
694  QString title = tr( "Relation %1 for %2." ).arg( mRelationName, mReferencingLayer->name() );
695  QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
696  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
697  mMessageBar->pushItem( mMessageBarItem );
698  }
699 }
700 
701 void QgsRelationReferenceWidget::comboReferenceChanged( int index )
702 {
703  QgsFeatureId fid = mComboBox->itemData( index, QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
704  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( mFeature );
705  highlightFeature( mFeature );
706  updateAttributeEditorFrame( mFeature );
707  emit foreignKeyChanged( mFeature.attribute( mReferencedFieldIdx ) );
708 }
709 
710 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature& feature )
711 {
712  mOpenFormButton->setEnabled( feature.isValid() );
713  // Check if we're running with an embedded frame we need to update
714  if ( mAttributeEditorFrame && mReferencedAttributeForm )
715  {
716  mReferencedAttributeForm->setFeature( feature );
717  }
718 }
719 
721 {
722  return mAllowAddFeatures;
723 }
724 
726 {
727  mAllowAddFeatures = allowAddFeatures;
728  updateAddEntryButton();
729 }
730 
731 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature& feature )
732 {
733  if ( mReadOnlySelector )
734  {
735  QgsExpression expr( mReferencedLayer->displayExpression() );
736  QgsExpressionContext context;
739  << QgsExpressionContextUtils::layerScope( mReferencedLayer );
740  context.setFeature( feature );
741  QString title = expr.evaluate( &context ).toString();
742  if ( expr.hasEvalError() )
743  {
744  title = feature.attribute( mReferencedFieldIdx ).toString();
745  }
746  mLineEdit->setText( title );
747  mForeignKey = feature.attribute( mReferencedFieldIdx );
748  mFeature = feature;
749  }
750  else
751  {
752  mComboBox->setCurrentIndex( mComboBox->findData( feature.id(), QgsAttributeTableModel::FeatureIdRole ) );
753  mFeature = feature;
754  }
755 
756  mRemoveFKButton->setEnabled( mIsEditable );
757  highlightFeature( feature );
758  updateAttributeEditorFrame( feature );
759  emit foreignKeyChanged( foreignKey() );
760 
761  unsetMapTool();
762 }
763 
764 void QgsRelationReferenceWidget::unsetMapTool()
765 {
766  // deactivate map tool if activated
767  if ( mCanvas && mMapTool )
768  {
769  /* this will call mapToolDeactivated */
770  mCanvas->unsetMapTool( mMapTool );
771  }
772 }
773 
774 void QgsRelationReferenceWidget::mapToolDeactivated()
775 {
776  if ( mWindowWidget )
777  {
778  mWindowWidget->raise();
779  mWindowWidget->activateWindow();
780  }
781 
782  if ( mMessageBar && mMessageBarItem )
783  {
784  mMessageBar->popWidget( mMessageBarItem );
785  }
786  mMessageBarItem = nullptr;
787 }
788 
789 void QgsRelationReferenceWidget::filterChanged()
790 {
791  QVariant nullValue = QSettings().value( QStringLiteral( "qgis/nullValue" ), "NULL" );
792 
793  QStringList filters;
794  QgsAttributeList attrs;
795 
796  QComboBox* scb = qobject_cast<QComboBox*>( sender() );
797 
798  Q_ASSERT( scb );
799 
800  if ( mChainFilters )
801  {
802  QComboBox* ccb = nullptr;
803  Q_FOREACH ( QComboBox* cb, mFilterComboBoxes )
804  {
805  if ( !ccb )
806  {
807  if ( cb == scb )
808  ccb = cb;
809 
810  continue;
811  }
812 
813  if ( ccb->currentIndex() == 0 )
814  {
815  cb->setCurrentIndex( 0 );
816  cb->setEnabled( false );
817  }
818  else
819  {
820  cb->blockSignals( true );
821  cb->clear();
822  cb->addItem( cb->property( "FieldAlias" ).toString() );
823 
824  // ccb = scb
825  // cb = scb + 1
826  QStringList texts;
827  Q_FOREACH ( const QString& txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
828  {
829  texts << txt;
830  }
831  texts.sort();
832  cb->addItems( texts );
833 
834  cb->setEnabled( true );
835  cb->blockSignals( false );
836 
837  ccb = cb;
838  }
839  }
840  }
841 
842  Q_FOREACH ( QComboBox* cb, mFilterComboBoxes )
843  {
844  if ( cb->currentIndex() != 0 )
845  {
846  const QString fieldName = cb->property( "Field" ).toString();
847 
848  if ( cb->currentText() == nullValue.toString() )
849  {
850  filters << QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
851  }
852  else
853  {
854  if ( mReferencedLayer->fields().field( fieldName ).type() == QVariant::String )
855  {
856  filters << QStringLiteral( "\"%1\" = '%2'" ).arg( fieldName, cb->currentText() );
857  }
858  else
859  {
860  filters << QStringLiteral( "\"%1\" = %2" ).arg( fieldName, cb->currentText() );
861  }
862  }
863  attrs << mReferencedLayer->fields().lookupField( fieldName );
864  }
865  }
866 
867  QString filterExpression = filters.join( QStringLiteral( " AND " ) );
868 
869  QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( filterExpression ).setSubsetOfAttributes( attrs ) ) );
870 
871  QgsFeature f;
872  QgsFeatureIds featureIds;
873 
874  while ( it.nextFeature( f ) )
875  {
876  featureIds << f.id();
877  }
878 
879  mFilterModel->setFilteredFeatures( featureIds );
880 }
881 
882 void QgsRelationReferenceWidget::addEntry()
883 {
884  QgsFeature f( mReferencedLayer->fields() );
885  QgsAttributeMap attributes;
886 
887  // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
888  if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
889  {
890  int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
891 
892  if ( fieldIdx != -1 )
893  {
894  attributes.insert( fieldIdx, mComboBox->currentText() );
895  }
896  }
897 
898  if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, QgsGeometry(), &f ) )
899  {
900  int i = mComboBox->findData( f.id(), QgsAttributeTableModel::FeatureIdRole );
901  mComboBox->setCurrentIndex( i );
902  mAddEntryButton->setEnabled( false );
903  }
904 }
905 
906 void QgsRelationReferenceWidget::updateAddEntryButton()
907 {
908  mAddEntryButton->setVisible( mAllowAddFeatures );
909  mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
910 }
void uniqueValues(int index, QList< QVariant > &uniqueValues, int limit=-1) const
Calculates a list of unique values contained within an attribute in the layer.
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:291
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:200
void setRequest(const QgsFeatureRequest &request)
Set a request that will be used to fill this attribute table model.
Class for parsing and evaluation of expressions (formerly called "search strings").
const QgsVectorLayerTools * vectorLayerTools() const
QgsFeatureId id
Definition: qgsfeature.h:139
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
bool isValid() const
Returns the validity of this relation.
static unsigned index
A rectangle specified with double values.
Definition: qgsrectangle.h:35
static double DEFAULT_HIGHLIGHT_MIN_WIDTH_MM
Default highlight line/outline minimum width in mm.
Definition: qgis.h:108
virtual void loadLayer()
Loads the layer into the model Preferably to be called, before using this model as source for any oth...
QgsPoint asPoint() const
Return contents of the geometry as a point if wkbType is WKBPoint, otherwise returns [0...
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.
QMap< int, QVariant > QgsAttributeMap
Definition: qgsfeature.h:44
bool setDisplayExpression(const QString &expression)
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:355
void deleteForeignKey()
unset the currently related feature
void foreignKeyChanged(const QVariant &)
This class contains context information for attribute editor widgets.
static QIcon getThemeIcon(const QString &theName)
Helper to get a theme icon.
void setOpenFormButtonVisible(bool openFormButtonVisible)
void scale(double scaleFactor, const QgsPoint *c=nullptr)
Scale the rectangle around its center point.
void setFillColor(const QColor &fillColor)
Set polygons fill color.
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:42
static const QColor DEFAULT_HIGHLIGHT_COLOR
Default highlight color.
Definition: qgis.h:100
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:78
QgsGeometry centroid() const
Returns the center of mass of a geometry.
QSet< QString > referencedColumns() const
Get list of columns referenced by the expression.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:135
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:214
virtual bool isEditable() const override
Returns true if the provider is in editing mode.
int count() const
Return number of items.
Definition: qgsfields.cpp:117
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:106
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:140
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:128
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.
QgsFields fields() const
Returns the list of fields of this layer.
QgsField at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:137
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 / outline buffer in millimeters.
Definition: qgshighlight.h:75
void setOrderByValue(bool orderByValue)
Set if the widget will order the combobox entries by value.
static QgsMessageBarItem * createMessage(const QString &text, QWidget *parent=nullptr)
make out a widget containing a message to be displayed on the bar
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
QgsFeatureRequest getReferencedFeatureRequest(const QgsAttributes &attributes) const
Creates a request to return the feature on the referenced (parent) layer which is referenced by the p...
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...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const
Query the layer for features specified in request.
QgsRectangle extent() const
Returns the current zoom exent of the map canvas.
A class for highlight features on the map.
Definition: qgshighlight.h:35
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.
QList< int > QgsAttributeList
QgsGeometry geometry() const
Returns the geometry associated with this feature.
Definition: qgsfeature.cpp:113
void setRelation(const QgsRelation &relation, bool allowNullValue)
A class to represent a point.
Definition: qgspoint.h:142
QString displayExpression
QList< FieldPair > fieldPairs() const
Returns the field pairs which form this relation The first element of each pair are the field names o...
QgsVectorLayer * referencedLayer() const
Access the referenced (parent) layer.
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:205
QgsVectorLayer * referencingLayer() const
Access the referencing (child) layer This is the layer which has the field(s) which point to another ...
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:176
void pushItem(QgsMessageBarItem *item)
Display a message item on the bar after hiding the currently visible one and putting it in a stack...
QgsPoint layerToMapCoordinates(QgsMapLayer *theLayer, QgsPoint point) const
transform point coordinates from layer&#39;s CRS to output CRS
void setColor(const QColor &color)
Set line/outline 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 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.
virtual bool addFeature(QgsVectorLayer *layer, const QgsAttributeMap &defaultValues=QgsAttributeMap(), const QgsGeometry &defaultGeometry=QgsGeometry(), QgsFeature *feature=nullptr) const =0
This method should/will be called, whenever a new feature will be added to the layer.
void setExtent(const QgsRectangle &r, bool magnified=false)
Set the extent of the map canvas.
qint64 QgsFeatureId
Definition: qgsfeature.h:32
QString name
Read property of QString layerName.
Definition: qgsmaplayer.h:53
void setInjectNull(bool injectNull)
If true is specified, a NULL value will be injected.
static QgsExpressionContextScope * projectScope()
Creates a new scope which contains variables and functions relating to the current QGIS project...
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
bool nextFeature(QgsFeature &f)
Geometry is not required. It may still be returned if e.g. required for a filter condition.
static double DEFAULT_HIGHLIGHT_BUFFER_MM
Default highlight buffer in mm.
Definition: qgis.h:104
A vector of attributes.
Definition: qgsfeature.h:55
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:98
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:277
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
QgsField field(int fieldIdx) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:142
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
QString name() const
Returns a human readable name for this relation.
void setChainFilters(bool chainFilters)
Set if filters are chained.
void zoomByFactor(double scaleFactor, const QgsPoint *center=nullptr)
Zoom with the factor supplied.
A form was embedded as a widget on another form.
void setMinWidth(double width)
Set minimum line / outline width in millimeters.
Definition: qgshighlight.h:79