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