QGIS API Documentation  2.17.0-Master (eef6f05)
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.svg" ) );
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  cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
470  mFilterComboBoxes << cb;
471  mReferencedLayer->uniqueValues( idx, uniqueValues );
472  cb->addItem( mReferencedLayer->attributeDisplayName( idx ) );
473  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
474  cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->fields().at( idx ).type() ) );
475 
476  qSort( uniqueValues.begin(), uniqueValues.end(), qgsVariantLessThan );
477  Q_FOREACH ( const QVariant& v, uniqueValues )
478  {
479  cb->addItem( v.toString(), v );
480  }
481 
482  connect( cb, SIGNAL( currentIndexChanged( int ) ), this, SLOT( filterChanged() ) );
483 
484  // Request this attribute for caching
485  requestedAttrs << fieldName;
486 
487  mFilterLayout->addWidget( cb );
488  }
489 
490  if ( mChainFilters )
491  {
492  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
493 
494  QgsFeature ft;
495  QgsFeatureIterator fit = layerCache->getFeatures();
496  while ( fit.nextFeature( ft ) )
497  {
498  for ( int i = 0; i < mFilterComboBoxes.count() - 1; ++i )
499  {
500  QVariant cv = ft.attribute( mFilterFields.at( i ) );
501  QVariant nv = ft.attribute( mFilterFields.at( i + 1 ) );
502  QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
503  QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
504  mFilterCache[mFilterFields[i]][cf] << nf;
505  }
506  }
507  }
508  }
509  else
510  {
511  mFilterContainer->hide();
512  }
513 
514  QgsExpression exp( mReferencedLayer->displayExpression() );
515 
516  requestedAttrs += exp.referencedColumns().toSet();
517  requestedAttrs << mRelation.fieldPairs().at( 0 ).second;
518 
519  QgsAttributeList attributes;
520  Q_FOREACH ( const QString& attr, requestedAttrs )
521  attributes << mReferencedLayer->fieldNameIndex( attr );
522 
523  layerCache->setCacheSubsetOfAttributes( attributes );
524  mMasterModel = new QgsAttributeTableModel( layerCache );
525  mMasterModel->setRequest( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( requestedAttrs.toList(), mReferencedLayer->fields() ) );
526  mFilterModel = new QgsAttributeTableFilterModel( mCanvas, mMasterModel, mMasterModel );
527  mFeatureListModel = new QgsFeatureListModel( mFilterModel, this );
528  mFeatureListModel->setDisplayExpression( mReferencedLayer->displayExpression() );
529 
530  mMasterModel->loadLayer();
531 
532  mFeatureListModel->setInjectNull( mAllowNull );
533  if ( mOrderByValue )
534  {
535  mFilterModel->sort( mReferencedLayer->displayExpression() );
536  }
537 
538  mComboBox->setModel( mFeatureListModel );
539 
540  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
541 
542  if ( mChainFilters && mFeature.isValid() )
543  {
544  for ( int i = 0; i < mFilterFields.size(); i++ )
545  {
546  QVariant v = mFeature.attribute( mFilterFields[i] );
547  QString f = v.isNull() ? nullValue.toString() : v.toString();
548  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
549  }
550  }
551 
552  QVariant featId = mFeature.isValid() ? mFeature.id() : QVariant( QVariant::Int );
553  mComboBox->setCurrentIndex( mComboBox->findData( featId, QgsAttributeTableModel::FeatureIdRole ) );
554 
555  // Only connect after iterating, to have only one iterator on the referenced table at once
556  connect( mComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( comboReferenceChanged( int ) ) );
557  updateAttributeEditorFrame( mFeature );
559  }
560 }
561 
562 void QgsRelationReferenceWidget::highlightActionTriggered( QAction* action )
563 {
564  if ( action == mHighlightFeatureAction )
565  {
566  highlightFeature();
567  }
568  else if ( action == mScaleHighlightFeatureAction )
569  {
570  highlightFeature( QgsFeature(), Scale );
571  }
572  else if ( action == mPanHighlightFeatureAction )
573  {
574  highlightFeature( QgsFeature(), Pan );
575  }
576 }
577 
579 {
580  QgsFeature feat = referencedFeature();
581 
582  if ( !feat.isValid() )
583  return;
584 
586  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
587  attributeDialog.exec();
588 }
589 
590 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
591 {
592  if ( !mCanvas )
593  return;
594 
595  if ( !f.isValid() )
596  {
597  f = referencedFeature();
598  if ( !f.isValid() )
599  return;
600  }
601 
602  if ( !f.constGeometry() )
603  {
604  return;
605  }
606 
607  const QgsGeometry* geom = f.constGeometry();
608 
609  // scale or pan
610  if ( canvasExtent == Scale )
611  {
612  QgsRectangle featBBox = geom->boundingBox();
613  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
614  QgsRectangle extent = mCanvas->extent();
615  if ( !extent.contains( featBBox ) )
616  {
617  extent.combineExtentWith( featBBox );
618  extent.scale( 1.1 );
619  mCanvas->setExtent( extent );
620  mCanvas->refresh();
621  }
622  }
623  else if ( canvasExtent == Pan )
624  {
625  QgsGeometry* centroid = geom->centroid();
626  QgsPoint center = centroid->asPoint();
627  delete centroid;
628  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
629  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
630  }
631 
632  // highlight
633  deleteHighlight();
634  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
635  QSettings settings;
636  QColor color = QColor( settings.value( "/Map/highlight/color", QGis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
637  int alpha = settings.value( "/Map/highlight/colorAlpha", QGis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
638  double buffer = settings.value( "/Map/highlight/buffer", QGis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
639  double minWidth = settings.value( "/Map/highlight/minWidth", QGis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
640 
641  mHighlight->setColor( color ); // sets also fill with default alpha
642  color.setAlpha( alpha );
643  mHighlight->setFillColor( color ); // sets fill with alpha
644  mHighlight->setBuffer( buffer );
645  mHighlight->setMinWidth( minWidth );
646  mHighlight->show();
647 
648  QTimer* timer = new QTimer( this );
649  timer->setSingleShot( true );
650  connect( timer, SIGNAL( timeout() ), this, SLOT( deleteHighlight() ) );
651  timer->start( 3000 );
652 }
653 
654 void QgsRelationReferenceWidget::deleteHighlight()
655 {
656  if ( mHighlight )
657  {
658  mHighlight->hide();
659  delete mHighlight;
660  }
661  mHighlight = nullptr;
662 }
663 
665 {
666  if ( !mAllowMapIdentification || !mReferencedLayer )
667  return;
668 
669  const QgsVectorLayerTools* tools = mEditorContext.vectorLayerTools();
670  if ( !tools )
671  return;
672  if ( !mCanvas )
673  return;
674 
675  mMapTool->setLayer( mReferencedLayer );
676  mCanvas->setMapTool( mMapTool );
677 
678  mWindowWidget = window();
679 
680  mCanvas->window()->raise();
681  mCanvas->activateWindow();
682  mCanvas->setFocus();
683 
684  connect( mMapTool, SIGNAL( featureIdentified( QgsFeature ) ), this, SLOT( featureIdentified( const QgsFeature ) ) );
685  connect( mMapTool, SIGNAL( deactivated() ), this, SLOT( mapToolDeactivated() ) );
686 
687  if ( mMessageBar )
688  {
689  QString title = tr( "Relation %1 for %2." ).arg( mRelationName, mReferencingLayer->name() );
690  QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
691  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
692  mMessageBar->pushItem( mMessageBarItem );
693  }
694 }
695 
696 void QgsRelationReferenceWidget::comboReferenceChanged( int index )
697 {
699  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( mFeature );
700  highlightFeature( mFeature );
701  updateAttributeEditorFrame( mFeature );
702  emit foreignKeyChanged( mFeature.attribute( mReferencedFieldIdx ) );
703 }
704 
705 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature& feature )
706 {
707  mOpenFormButton->setEnabled( feature.isValid() );
708  // Check if we're running with an embedded frame we need to update
709  if ( mAttributeEditorFrame && mReferencedAttributeForm )
710  {
711  mReferencedAttributeForm->setFeature( feature );
712  }
713 }
714 
716 {
717  return mAllowAddFeatures;
718 }
719 
721 {
722  mAllowAddFeatures = allowAddFeatures;
723  updateAddEntryButton();
724 }
725 
726 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature& feature )
727 {
728  if ( mReadOnlySelector )
729  {
730  QgsExpression expr( mReferencedLayer->displayExpression() );
731  QgsExpressionContext context;
734  << QgsExpressionContextUtils::layerScope( mReferencedLayer );
735  context.setFeature( feature );
736  QString title = expr.evaluate( &context ).toString();
737  if ( expr.hasEvalError() )
738  {
739  title = feature.attribute( mReferencedFieldIdx ).toString();
740  }
741  mLineEdit->setText( title );
742  mForeignKey = feature.attribute( mReferencedFieldIdx );
743  mFeature = feature;
744  }
745  else
746  {
747  mComboBox->setCurrentIndex( mComboBox->findData( feature.id(), QgsAttributeTableModel::FeatureIdRole ) );
748  mFeature = feature;
749  }
750 
751  mRemoveFKButton->setEnabled( mIsEditable );
752  highlightFeature( feature );
753  updateAttributeEditorFrame( feature );
754  emit foreignKeyChanged( foreignKey() );
755 
756  unsetMapTool();
757 }
758 
759 void QgsRelationReferenceWidget::unsetMapTool()
760 {
761  // deactivate map tool if activated
762  if ( mCanvas && mMapTool )
763  {
764  /* this will call mapToolDeactivated */
765  mCanvas->unsetMapTool( mMapTool );
766  }
767 }
768 
769 void QgsRelationReferenceWidget::mapToolDeactivated()
770 {
771  if ( mWindowWidget )
772  {
773  mWindowWidget->raise();
774  mWindowWidget->activateWindow();
775  }
776 
777  if ( mMessageBar && mMessageBarItem )
778  {
779  mMessageBar->popWidget( mMessageBarItem );
780  }
781  mMessageBarItem = nullptr;
782 }
783 
784 void QgsRelationReferenceWidget::filterChanged()
785 {
786  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
787 
788  QStringList filters;
789  QgsAttributeList attrs;
790 
791  QComboBox* scb = qobject_cast<QComboBox*>( sender() );
792 
793  Q_ASSERT( scb );
794 
795  if ( mChainFilters )
796  {
797  QComboBox* ccb = nullptr;
798  Q_FOREACH ( QComboBox* cb, mFilterComboBoxes )
799  {
800  if ( !ccb )
801  {
802  if ( cb == scb )
803  ccb = cb;
804 
805  continue;
806  }
807 
808  if ( ccb->currentIndex() == 0 )
809  {
810  cb->setCurrentIndex( 0 );
811  cb->setEnabled( false );
812  }
813  else
814  {
815  cb->blockSignals( true );
816  cb->clear();
817  cb->addItem( cb->property( "FieldAlias" ).toString() );
818 
819  // ccb = scb
820  // cb = scb + 1
821  QStringList texts;
822  Q_FOREACH ( const QString& txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
823  {
824  texts << txt;
825  }
826  texts.sort();
827  cb->addItems( texts );
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.
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:199
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
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
void setStyleSheet(const QString &styleSheet)
bool isValid() const
Returns the validity of this relation.
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
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...
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.
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.
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)
Calculates a list of unique values contained within an attribute in the layer.
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)
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.
virtual void setVisible(bool visible)
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:42
T value() const
int exec()
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:76
void setAlpha(int alpha)
QString itemText(int index) const
const QgsGeometry * constGeometry() const
Gets a const pointer to the geometry object associated with this feature.
Definition: qgsfeature.cpp:82
QString join(const QString &separator) const
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)
virtual bool isEditable() const override
Returns true if the provider is in editing mode.
void setIcon(const QIcon &icon)
int count() const
Return number of items.
Definition: qgsfield.cpp:402
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.
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:269
void setAllowMapIdentification(bool allowMapIdentification)
const QgsField & at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:422
int size() const
bool allowMapIdentification()
determines if the widge offers the possibility to select the related feature on the map (using a dedi...
QgsGeometry * centroid() const
Returns the center of mass of a geometry.
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.
QgsFields fields() const
Returns the list of fields of this layer.
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:64
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
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...
QgsFeatureRequest getReferencedFeatureRequest(const QgsAttributes &attributes) const
Creates a request to return the feature on the referenced (parent) layer which is referenced by the p...
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
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void raise()
QgsRectangle extent() const
Returns the current zoom exent of the map canvas.
A class for highlight features on the map.
Definition: qgshighlight.h:37
This class wraps a request for features to a vector layer (or directly its vector data provider)...
void setOverrideCursor(const QCursor &cursor)
QgsVectorLayerCache * layerCache() const
Returns the layer cache this model uses as backend.
void restoreOverrideCursor()
void setCheckable(bool)
void hide()
void setRelation(const QgsRelation &relation, bool allowNullValue)
void setSizePolicy(QSizePolicy)
void addWidget(QWidget *w)
A class to represent a point.
Definition: qgspoint.h:117
QVariant itemData(int index, int role) const
QgsFeatureId id() const
Get the feature ID for this feature.
Definition: qgsfeature.cpp:65
QList< FieldPair > fieldPairs() const
Returns the field pairs which form this relation The first element of each pair are the field names o...
bool blockSignals(bool block)
const QFont & font() const
QgsVectorLayer * referencedLayer() const
Access the referenced (parent) layer.
This class caches features of a given QgsVectorLayer.
void setItalic(bool enable)
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:204
bool setAlignment(QWidget *w, QFlags< Qt::AlignmentFlag > alignment)
QVariant value(const QString &key, const QVariant &defaultValue) const
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:333
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
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)
void setAllowAddFeatures(bool allowAddFeatures)
Determines if a button for adding new features should be shown.
QWidget(QWidget *parent, QFlags< Qt::WindowType > f)
QgsRectangle boundingBox() const
Returns the bounding box of this feature.
void setPopupMode(ToolButtonPopupMode mode)
void showIndeterminateState()
Sets the widget to display in an indeterminate "mixed value" state.
void setCurrentIndex(int index)
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:31
void setText(const QString &text)
void start(int msec)
bool isValid() const
double toDouble(bool *ok) const
QString name
Read property of QString layerName.
Definition: qgsmaplayer.h:53
bool setProperty(const char *name, const QVariant &value)
const QgsField & field(int fieldIdx) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:427
void addItems(const QStringList &texts)
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.
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)
QString displayExpression() const
Get the preview expression, used to create a human readable preview string.
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:97
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:271
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
QString attributeDisplayName(int attributeIndex) const
Convenience function that returns the attribute alias if defined or the field name else...
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
int fieldNameIndex(const QString &fieldName) const
Returns the index of a field name or -1 if the field does not exist.
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 addLayout(QLayout *layout, int stretch)
void setMinWidth(double width)
Set minimum line / outline width in millimeters.
Definition: qgshighlight.h:68
void setSingleShot(bool singleShot)