QGIS API Documentation  2.17.0-Master (6f7b933)
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  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  std::sort( 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  const QStringList referencedColumns = QgsExpression( mReferencedLayer->displayExpression() ).referencedColumns();
536  if ( !referencedColumns.isEmpty() )
537  {
538  int sortIdx = mReferencedLayer->fieldNameIndex( referencedColumns.first() );
539  mFilterModel->sort( sortIdx );
540  }
541  }
542 
543  mComboBox->setModel( mFeatureListModel );
544 
545  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
546 
547  if ( mChainFilters && mFeature.isValid() )
548  {
549  for ( int i = 0; i < mFilterFields.size(); i++ )
550  {
551  QVariant v = mFeature.attribute( mFilterFields[i] );
552  QString f = v.isNull() ? nullValue.toString() : v.toString();
553  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
554  }
555  }
556 
557  QVariant featId = mFeature.isValid() ? mFeature.id() : QVariant( QVariant::Int );
558  mComboBox->setCurrentIndex( mComboBox->findData( featId, QgsAttributeTableModel::FeatureIdRole ) );
559 
560  // Only connect after iterating, to have only one iterator on the referenced table at once
561  connect( mComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( comboReferenceChanged( int ) ) );
562  updateAttributeEditorFrame( mFeature );
564  }
565 }
566 
567 void QgsRelationReferenceWidget::highlightActionTriggered( QAction* action )
568 {
569  if ( action == mHighlightFeatureAction )
570  {
571  highlightFeature();
572  }
573  else if ( action == mScaleHighlightFeatureAction )
574  {
575  highlightFeature( QgsFeature(), Scale );
576  }
577  else if ( action == mPanHighlightFeatureAction )
578  {
579  highlightFeature( QgsFeature(), Pan );
580  }
581 }
582 
584 {
585  QgsFeature feat = referencedFeature();
586 
587  if ( !feat.isValid() )
588  return;
589 
591  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
592  attributeDialog.exec();
593 }
594 
595 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
596 {
597  if ( !mCanvas )
598  return;
599 
600  if ( !f.isValid() )
601  {
602  f = referencedFeature();
603  if ( !f.isValid() )
604  return;
605  }
606 
607  if ( !f.constGeometry() )
608  {
609  return;
610  }
611 
612  const QgsGeometry* geom = f.constGeometry();
613 
614  // scale or pan
615  if ( canvasExtent == Scale )
616  {
617  QgsRectangle featBBox = geom->boundingBox();
618  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
619  QgsRectangle extent = mCanvas->extent();
620  if ( !extent.contains( featBBox ) )
621  {
622  extent.combineExtentWith( featBBox );
623  extent.scale( 1.1 );
624  mCanvas->setExtent( extent );
625  mCanvas->refresh();
626  }
627  }
628  else if ( canvasExtent == Pan )
629  {
630  QgsGeometry* centroid = geom->centroid();
631  QgsPoint center = centroid->asPoint();
632  delete centroid;
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( "/Map/highlight/color", QGis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
642  int alpha = settings.value( "/Map/highlight/colorAlpha", QGis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
643  double buffer = settings.value( "/Map/highlight/buffer", QGis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
644  double minWidth = settings.value( "/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 {
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( "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 << QString( "\"%1\" IS NULL" ).arg( fieldName );
851  }
852  else
853  {
854  if ( mReferencedLayer->fields().field( fieldName ).type() == QVariant::String )
855  {
856  filters << QString( "\"%1\" = '%2'" ).arg( fieldName, cb->currentText() );
857  }
858  else
859  {
860  filters << QString( "\"%1\" = %2" ).arg( fieldName, cb->currentText() );
861  }
862  }
863  attrs << mReferencedLayer->fieldNameIndex( fieldName );
864  }
865  }
866 
867  QString filterExpression = filters.join( " 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->fieldNameIndex( 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 }
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:395
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
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)
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)
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.
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)
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:64
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
QString attributeDisplayName(int attributeIndex) const
Convenience function that returns the attribute alias if defined or the field name else...
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()
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)
void restoreOverrideCursor()
int count() const
Return number of items.
Definition: qgsfield.cpp:370
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.
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:390
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:333
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
QString name
Read property of QString layerName.
Definition: qgsmaplayer.h:53
bool setProperty(const char *name, const QVariant &value)
QgsRectangle extent() const
Returns the current zoom exent of the map canvas.
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.
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:68
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)