QGIS API Documentation  2.9.0-Master
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 dot kuhn at gmx 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 
39 {
40  switch ( p1.first.type() )
41  {
42  case QVariant::String:
43  return p1.first.toString() < p2.first.toString();
44  break;
45 
46  case QVariant::Double:
47  return p1.first.toDouble() < p2.first.toDouble();
48  break;
49 
50  default:
51  return p1.first.toInt() < p2.first.toInt();
52  break;
53  }
54 }
55 
57  : QWidget( parent )
58  , mEditorContext( QgsAttributeEditorContext() )
59  , mCanvas( NULL )
60  , mMessageBar( NULL )
61  , mForeignKey( QVariant() )
62  , mFkeyFieldIdx( -1 )
63  , mAllowNull( true )
64  , mHighlight( NULL )
65  , mMapTool( NULL )
66  , mMessageBarItem( NULL )
67  , mRelationName( "" )
68  , mReferencedAttributeForm( NULL )
69  , mReferencedLayer( NULL )
70  , mReferencingLayer( NULL )
71  , mMasterModel( 0 )
72  , mFilterModel( 0 )
73  , mFeatureListModel( 0 )
74  , mWindowWidget( NULL )
75  , mShown( false )
76  , mIsEditable( true )
77  , mEmbedForm( false )
78  , mReadOnlySelector( false )
79  , mAllowMapIdentification( false )
80  , mOrderByValue( false )
81  , mOpenFormButtonVisible( true )
82 {
83  mTopLayout = new QVBoxLayout( this );
84  mTopLayout->setContentsMargins( 0, 0, 0, 0 );
85  mTopLayout->setAlignment( Qt::AlignTop );
86  setLayout( mTopLayout );
87 
88  QHBoxLayout* editLayout = new QHBoxLayout();
89  editLayout->setContentsMargins( 0, 0, 0, 0 );
90  editLayout->setSpacing( 2 );
91 
92  // Prepare the container and layout for the filter comboboxes
93  mChooserGroupBox = new QGroupBox( this );
94  editLayout->addWidget( mChooserGroupBox );
95  QHBoxLayout* chooserLayout = new QHBoxLayout;
96  chooserLayout->setContentsMargins( 0, 0, 0, 0 );
97  mFilterLayout = new QHBoxLayout;
98  mFilterLayout->setContentsMargins( 0, 0, 0, 0 );
99  mFilterContainer = new QWidget;
100  mFilterContainer->setLayout( mFilterLayout );
101  mChooserGroupBox->setLayout( chooserLayout );
102  chooserLayout->addWidget( mFilterContainer );
103 
104  // combobox (for non-geometric relation)
105  mComboBox = new QComboBox( this );
106  mChooserGroupBox->layout()->addWidget( mComboBox );
107 
108  // read-only line edit
109  mLineEdit = new QLineEdit( this );
110  mLineEdit->setReadOnly( true );
111  editLayout->addWidget( mLineEdit );
112 
113  // open form button
114  mOpenFormButton = new QToolButton( this );
115  mOpenFormButton->setIcon( QgsApplication::getThemeIcon( "/mActionPropertyItem.png" ) );
116  mOpenFormButton->setText( tr( "Open related feature form" ) );
117  editLayout->addWidget( mOpenFormButton );
118 
119  // highlight button
120  mHighlightFeatureButton = new QToolButton( this );
121  mHighlightFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
122  mHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( "/mActionHighlightFeature.svg" ), tr( "Highlight feature" ), this );
123  mScaleHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( "/mActionScaleHighlightFeature.svg" ), tr( "Scale and highlight feature" ), this );
124  mPanHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( "/mActionPanHighlightFeature.svg" ), tr( "Pan and highlight feature" ), this );
125  mHighlightFeatureButton->addAction( mHighlightFeatureAction );
126  mHighlightFeatureButton->addAction( mScaleHighlightFeatureAction );
127  mHighlightFeatureButton->addAction( mPanHighlightFeatureAction );
128  mHighlightFeatureButton->setDefaultAction( mHighlightFeatureAction );
129  editLayout->addWidget( mHighlightFeatureButton );
130 
131  // map identification button
132  mMapIdentificationButton = new QToolButton( this );
133  mMapIdentificationButton->setIcon( QgsApplication::getThemeIcon( "/mActionMapIdentification.svg" ) );
134  mMapIdentificationButton->setText( tr( "Select on map" ) );
135  mMapIdentificationButton->setCheckable( true );
136  editLayout->addWidget( mMapIdentificationButton );
137 
138  // remove foreign key button
139  mRemoveFKButton = new QToolButton( this );
140  mRemoveFKButton->setIcon( QgsApplication::getThemeIcon( "/mActionRemove.svg" ) );
141  mRemoveFKButton->setText( tr( "No selection" ) );
142  editLayout->addWidget( mRemoveFKButton );
143 
144  // spacer
145  editLayout->addItem( new QSpacerItem( 0, 0, QSizePolicy::Expanding ) );
146 
147  // add line to top layout
148  mTopLayout->addLayout( editLayout );
149 
150  // embed form
151  mAttributeEditorFrame = new QgsCollapsibleGroupBox( this );
152  mAttributeEditorLayout = new QVBoxLayout( mAttributeEditorFrame );
153  mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
154  mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
155  mTopLayout->addWidget( mAttributeEditorFrame );
156 
157  // invalid label
158  mInvalidLabel = new QLabel( tr( "The relation is not valid. Please make sure your relation definitions are ok." ) );
159  mInvalidLabel->setWordWrap( true );
160  QFont font = mInvalidLabel->font();
161  font.setItalic( true );
162  mInvalidLabel->setStyleSheet( "QLabel { color: red; } " );
163  mInvalidLabel->setFont( font );
164  mTopLayout->addWidget( mInvalidLabel );
165 
166  // default mode is combobox, no geometric relation and no embed form
167  mLineEdit->hide();
168  mMapIdentificationButton->hide();
169  mHighlightFeatureButton->hide();
170  mAttributeEditorFrame->hide();
171  mInvalidLabel->hide();
172 
173  // connect buttons
174  connect( mOpenFormButton, SIGNAL( clicked() ), this, SLOT( openForm() ) );
175  connect( mHighlightFeatureButton, SIGNAL( triggered( QAction* ) ), this, SLOT( highlightActionTriggered( QAction* ) ) );
176  connect( mMapIdentificationButton, SIGNAL( clicked() ), this, SLOT( mapIdentification() ) );
177  connect( mRemoveFKButton, SIGNAL( clicked() ), this, SLOT( deleteForeignKey() ) );
178 }
179 
181 {
182  deleteHighlight();
183  unsetMapTool();
184  if ( mMapTool )
185  delete mMapTool;
186 }
187 
188 void QgsRelationReferenceWidget::setRelation( QgsRelation relation, bool allowNullValue )
189 {
190  mAllowNull = allowNullValue;
191  mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
192 
193  if ( relation.isValid() )
194  {
195  mInvalidLabel->hide();
196 
197  mRelation = relation;
198  mReferencingLayer = relation.referencingLayer();
199  mRelationName = relation.name();
200  mReferencedLayer = relation.referencedLayer();
201  mFkeyFieldIdx = mReferencedLayer->fieldNameIndex( relation.fieldPairs().first().second );
202 
204 
205  if ( mEmbedForm )
206  {
207  mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
208  mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
209  mReferencedAttributeForm->hideButtonBox();
210  mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
211  }
212  }
213  else
214  {
215  mInvalidLabel->show();
216  }
217 
218  if ( mShown && isVisible() )
219  {
220  init();
221  }
222 }
223 
225 {
226  if ( !editable )
227  unsetMapTool();
228 
229  mFilterContainer->setEnabled( editable );
230  mComboBox->setEnabled( editable );
231  mMapIdentificationButton->setEnabled( editable );
232  mRemoveFKButton->setEnabled( editable );
233  mIsEditable = editable;
234 }
235 
236 void QgsRelationReferenceWidget::setForeignKey( const QVariant& value )
237 {
238  if ( !value.isValid() || value.isNull() )
239  {
241  return;
242  }
243 
244  if ( !mReferencedLayer )
245  return;
246 
247  QgsFeatureIterator fit;
248 
249  // TODO: Rewrite using expression
250  if ( mMasterModel )
251  {
252  fit = mMasterModel->layerCache()->getFeatures( QgsFeatureRequest() );
253  }
254  else
255  {
256  fit = mReferencedLayer->getFeatures( QgsFeatureRequest() );
257  }
258  while ( fit.nextFeature( mFeature ) )
259  {
260  if ( mFeature.attribute( mFkeyFieldIdx ) == value )
261  {
262  break;
263  }
264  }
265 
266  if ( !mFeature.isValid() )
267  {
269  return;
270  }
271 
272  mForeignKey = mFeature.attribute( mFkeyFieldIdx );
273 
274  if ( mReadOnlySelector )
275  {
276  QgsExpression expr( mReferencedLayer->displayExpression() );
277  QString title = expr.evaluate( &mFeature ).toString();
278  if ( expr.hasEvalError() )
279  {
280  title = mFeature.attribute( mFkeyFieldIdx ).toString();
281  }
282  mLineEdit->setText( title );
283  }
284  else
285  {
286  int i = mComboBox->findData( value, QgsAttributeTableModel::FeatureIdRole );
287  if ( i == -1 && mAllowNull )
288  {
289  mComboBox->setCurrentIndex( 0 );
290  }
291  else
292  {
293  mComboBox->setCurrentIndex( i );
294  }
295  }
296 
297  mRemoveFKButton->setEnabled( mIsEditable );
298  highlightFeature( mFeature );
299  updateAttributeEditorFrame( mFeature );
300  emit foreignKeyChanged( foreignKey() );
301 }
302 
304 {
305  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
306  if ( mReadOnlySelector )
307  {
308  QString nullText = "";
309  if ( mAllowNull )
310  {
311  nullText = tr( "%1 (no selection)" ).arg( nullValue.toString() );
312  }
313  mLineEdit->setText( nullText );
314  mForeignKey = QVariant();
315  mFeature.setValid( false );
316  }
317  else
318  {
319  if ( mAllowNull )
320  {
321  mComboBox->setCurrentIndex( 0 );
322  }
323  else
324  {
325  mComboBox->setCurrentIndex( -1 );
326  }
327  }
328  mRemoveFKButton->setEnabled( false );
329  updateAttributeEditorFrame( QgsFeature() );
330  emit foreignKeyChanged( QVariant( QVariant::Int ) );
331 }
332 
334 {
335  QgsFeature f;
336  if ( mReferencedLayer )
337  {
338  QgsFeatureId fid;
339  if ( mReadOnlySelector )
340  {
341  fid = mFeature.id();
342  }
343  else
344  {
345  fid = mComboBox->itemData( mComboBox->currentIndex(), QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
346  }
347  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( f );
348  }
349  return f;
350 }
351 
353 {
354  if ( mReadOnlySelector )
355  {
356  return mForeignKey;
357  }
358  else
359  {
360  QVariant varFid = mComboBox->itemData( mComboBox->currentIndex(), QgsAttributeTableModel::FeatureIdRole );
361  if ( varFid.isNull() )
362  {
363  return QVariant();
364  }
365  else
366  {
367  return mFeature.attribute( mFkeyFieldIdx );
368  }
369  }
370 }
371 
373 {
374  mEditorContext = context;
375  mCanvas = canvas;
376  mMessageBar = messageBar;
377 
378  if ( mMapTool )
379  delete mMapTool;
380  mMapTool = new QgsMapToolIdentifyFeature( mCanvas );
381  mMapTool->setAction( mMapIdentificationButton->defaultAction() );
382 }
383 
385 {
386  mAttributeEditorFrame->setVisible( display );
387  mEmbedForm = display;
388 }
389 
391 {
392  mChooserGroupBox->setHidden( readOnly );
393  mLineEdit->setVisible( readOnly );
394  mRemoveFKButton->setVisible( mAllowNull && readOnly );
395  mReadOnlySelector = readOnly;
396 }
397 
398 void QgsRelationReferenceWidget::setAllowMapIdentification( bool allowMapIdentification )
399 {
400  mHighlightFeatureButton->setVisible( allowMapIdentification );
401  mMapIdentificationButton->setVisible( allowMapIdentification );
402  mAllowMapIdentification = allowMapIdentification;
403 }
404 
406 {
407  mOrderByValue = orderByValue;
408 }
409 
410 void QgsRelationReferenceWidget::setFilterFields( QStringList filterFields )
411 {
412  mFilterFields = filterFields;
413 }
414 
416 {
417  mOpenFormButton->setVisible( openFormButtonVisible );
418  mOpenFormButtonVisible = openFormButtonVisible;
419 }
420 
422 {
423  mChainFilters = chainFilters;
424 }
425 
427 {
428  Q_UNUSED( e )
429 
430  mShown = true;
431 
432  init();
433 }
434 
436 {
437  if ( !mReadOnlySelector && mComboBox->count() == 0 && mReferencedLayer )
438  {
439  QApplication::setOverrideCursor( Qt::WaitCursor );
440 
441  QSet<QString> requestedAttrs;
442 
443  QgsVectorLayerCache* layerCache = new QgsVectorLayerCache( mReferencedLayer, 100000, this );
444 
445  if ( mFilterFields.size() )
446  {
447  Q_FOREACH ( const QString& fieldName, mFilterFields )
448  {
449  QVariantList uniqueValues;
450  int idx = mReferencedLayer->fieldNameIndex( fieldName );
451  QComboBox* cb = new QComboBox();
452  cb->setProperty( "Field", fieldName );
453  mFilterComboBoxes << cb;
454  mReferencedLayer->uniqueValues( idx, uniqueValues );
455  cb->addItem( mReferencedLayer->attributeAlias( idx ).isEmpty() ? fieldName : mReferencedLayer->attributeAlias( idx ) );
456  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
457  cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->pendingFields()[idx].type() ) );
458 
459  Q_FOREACH ( QVariant v, uniqueValues )
460  {
461  cb->addItem( v.toString(), v );
462  }
463 
464  connect( cb, SIGNAL( currentIndexChanged( int ) ), this, SLOT( filterChanged() ) );
465 
466  // Request this attribute for caching
467  requestedAttrs << fieldName;
468 
469  mFilterLayout->addWidget( cb );
470  }
471 
472  if ( mChainFilters )
473  {
474  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
475 
476  QgsFeature ft;
477  QgsFeatureIterator fit = layerCache->getFeatures();
478  while ( fit.nextFeature( ft ) )
479  {
480  for ( int i = 0; i < mFilterComboBoxes.count() - 1; ++i )
481  {
482  QVariant cv = ft.attribute( mFilterFields[i] );
483  QVariant nv = ft.attribute( mFilterFields[i + 1] );
484  QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
485  QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
486  mFilterCache[mFilterFields[i]][cf] << nf;
487  }
488  }
489  }
490  }
491 
492  QgsExpression exp( mReferencedLayer->displayExpression() );
493 
494  requestedAttrs += exp.referencedColumns().toSet();
495  requestedAttrs << mRelation.fieldPairs().first().second;
496 
497  QgsAttributeList attributes;
498  Q_FOREACH ( const QString& attr, requestedAttrs )
499  attributes << mReferencedLayer->fieldNameIndex( attr );
500 
501  layerCache->setCacheSubsetOfAttributes( attributes );
502  mMasterModel = new QgsAttributeTableModel( layerCache );
503  mMasterModel->setRequest( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( requestedAttrs.toList(), mReferencedLayer->pendingFields() ) );
504  mFilterModel = new QgsAttributeTableFilterModel( mCanvas, mMasterModel, mMasterModel );
505  mFeatureListModel = new QgsFeatureListModel( mFilterModel, this );
506  mFeatureListModel->setDisplayExpression( mReferencedLayer->displayExpression() );
507 
508  mMasterModel->loadLayer();
509 
510  mFeatureListModel->setInjectNull( mAllowNull );
511  if ( mOrderByValue )
512  {
513  const QStringList referencedColumns = QgsExpression( mReferencedLayer->displayExpression() ).referencedColumns();
514  if ( referencedColumns.size() > 0 )
515  {
516  int sortIdx = mReferencedLayer->fieldNameIndex( referencedColumns.first() );
517  mFilterModel->sort( sortIdx );
518  }
519  }
520 
521  mComboBox->setModel( mFeatureListModel );
522 
523  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
524 
525  if ( mChainFilters && mFeature.isValid() )
526  {
527  for ( int i = 0; i < mFilterFields.size(); i++ )
528  {
529  QVariant v = mFeature.attribute( mFilterFields[i] );
530  QString f = v.isNull() ? nullValue.toString() : v.toString();
531  mFilterComboBoxes[i]->setCurrentIndex( mFilterComboBoxes[i]->findText( f ) );
532  }
533  }
534 
535  mComboBox->setCurrentIndex( mComboBox->findData( mFeature.id(), QgsAttributeTableModel::FeatureIdRole ) );
536 
537  // Only connect after iterating, to have only one iterator on the referenced table at once
538  connect( mComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( comboReferenceChanged( int ) ) );
539  QApplication::restoreOverrideCursor();
540  }
541 }
542 
543 void QgsRelationReferenceWidget::highlightActionTriggered( QAction* action )
544 {
545  if ( action == mHighlightFeatureAction )
546  {
547  highlightFeature();
548  }
549  else if ( action == mScaleHighlightFeatureAction )
550  {
551  highlightFeature( QgsFeature(), Scale );
552  }
553  else if ( action == mPanHighlightFeatureAction )
554  {
555  highlightFeature( QgsFeature(), Pan );
556  }
557 }
558 
560 {
561  QgsFeature feat = referencedFeature();
562 
563  if ( !feat.isValid() )
564  return;
565 
567  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
568  attributeDialog.exec();
569 }
570 
571 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
572 {
573  if ( !mCanvas )
574  return;
575 
576  if ( !f.isValid() )
577  {
578  f = referencedFeature();
579  if ( !f.isValid() )
580  return;
581  }
582 
583  if ( !f.constGeometry() )
584  {
585  return;
586  }
587 
588  const QgsGeometry* geom = f.constGeometry();
589 
590  // scale or pan
591  if ( canvasExtent == Scale )
592  {
593  QgsRectangle featBBox = geom->boundingBox();
594  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
595  QgsRectangle extent = mCanvas->extent();
596  if ( !extent.contains( featBBox ) )
597  {
598  extent.combineExtentWith( &featBBox );
599  extent.scale( 1.1 );
600  mCanvas->setExtent( extent );
601  mCanvas->refresh();
602  }
603  }
604  else if ( canvasExtent == Pan )
605  {
606  QgsGeometry* centroid = geom->centroid();
607  QgsPoint center = centroid->asPoint();
608  delete centroid;
609  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
610  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
611  }
612 
613  // highlight
614  deleteHighlight();
615  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
616  QSettings settings;
617  QColor color = QColor( settings.value( "/Map/highlight/color", QGis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
618  int alpha = settings.value( "/Map/highlight/colorAlpha", QGis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
619  double buffer = settings.value( "/Map/highlight/buffer", QGis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
620  double minWidth = settings.value( "/Map/highlight/minWidth", QGis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
621 
622  mHighlight->setColor( color ); // sets also fill with default alpha
623  color.setAlpha( alpha );
624  mHighlight->setFillColor( color ); // sets fill with alpha
625  mHighlight->setBuffer( buffer );
626  mHighlight->setMinWidth( minWidth );
627  mHighlight->show();
628 
629  QTimer* timer = new QTimer( this );
630  timer->setSingleShot( true );
631  connect( timer, SIGNAL( timeout() ), this, SLOT( deleteHighlight() ) );
632  timer->start( 3000 );
633 }
634 
635 void QgsRelationReferenceWidget::deleteHighlight()
636 {
637  if ( mHighlight )
638  {
639  mHighlight->hide();
640  delete mHighlight;
641  }
642  mHighlight = NULL;
643 }
644 
646 {
647  if ( !mAllowMapIdentification || !mReferencedLayer )
648  return;
649 
650  const QgsVectorLayerTools* tools = mEditorContext.vectorLayerTools();
651  if ( !tools )
652  return;
653  if ( !mCanvas )
654  return;
655 
656  mMapTool->setLayer( mReferencedLayer );
657  mCanvas->setMapTool( mMapTool );
658 
659  mWindowWidget = window();
660 
661  mCanvas->window()->raise();
662  mCanvas->activateWindow();
663  mCanvas->setFocus();
664 
665  connect( mMapTool, SIGNAL( featureIdentified( QgsFeature ) ), this, SLOT( featureIdentified( const QgsFeature ) ) );
666  connect( mMapTool, SIGNAL( deactivated() ), this, SLOT( mapToolDeactivated() ) );
667 
668  if ( mMessageBar )
669  {
670  QString title = QString( "Relation %1 for %2." ).arg( mRelationName ).arg( mReferencingLayer->name() );
671  QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
672  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
673  mMessageBar->pushItem( mMessageBarItem );
674  }
675 }
676 
677 void QgsRelationReferenceWidget::comboReferenceChanged( int index )
678 {
679  QgsFeatureId fid = mComboBox->itemData( index, QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
680  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( mFeature );
681  highlightFeature( mFeature );
682  updateAttributeEditorFrame( mFeature );
683  emit foreignKeyChanged( mFeature.attribute( mFkeyFieldIdx ) );
684 }
685 
686 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature feature )
687 {
688  // Check if we're running with an embedded frame we need to update
689  if ( mAttributeEditorFrame )
690  {
691  if ( mReferencedAttributeForm )
692  {
693  mReferencedAttributeForm->setFeature( feature );
694  }
695  }
696 }
697 
698 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature& feature )
699 {
700  if ( mReadOnlySelector )
701  {
702  QgsExpression expr( mReferencedLayer->displayExpression() );
703  QString title = expr.evaluate( &feature ).toString();
704  if ( expr.hasEvalError() )
705  {
706  title = feature.attribute( mFkeyFieldIdx ).toString();
707  }
708  mLineEdit->setText( title );
709  mForeignKey = feature.attribute( mFkeyFieldIdx );
710  mFeature = feature;
711  }
712  else
713  {
714  mComboBox->setCurrentIndex( mComboBox->findData( feature.attribute( mFkeyFieldIdx ), QgsAttributeTableModel::FeatureIdRole ) );
715  mFeature = feature;
716  }
717 
718  mRemoveFKButton->setEnabled( mIsEditable );
719  highlightFeature( feature );
720  updateAttributeEditorFrame( feature );
721  emit foreignKeyChanged( foreignKey() );
722 
723  unsetMapTool();
724 }
725 
726 void QgsRelationReferenceWidget::unsetMapTool()
727 {
728  // deactivate map tool if activated
729  if ( mCanvas && mMapTool )
730  {
731  /* this will call mapToolDeactivated */
732  mCanvas->unsetMapTool( mMapTool );
733  }
734 }
735 
736 void QgsRelationReferenceWidget::mapToolDeactivated()
737 {
738  if ( mWindowWidget )
739  {
740  mWindowWidget->raise();
741  mWindowWidget->activateWindow();
742  }
743 
744  if ( mMessageBar && mMessageBarItem )
745  {
746  mMessageBar->popWidget( mMessageBarItem );
747  }
748  mMessageBarItem = NULL;
749 }
750 
751 void QgsRelationReferenceWidget::filterChanged()
752 {
753  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
754 
755  QStringList filters;
756  QgsAttributeList attrs;
757 
758  QComboBox* scb = qobject_cast<QComboBox*>( sender() );
759 
760  Q_ASSERT( scb );
761 
762  if ( mChainFilters )
763  {
764  QComboBox* ccb = 0;
765  Q_FOREACH ( QComboBox* cb, mFilterComboBoxes )
766  {
767  if ( ccb == 0 )
768  {
769  if ( cb != scb )
770  continue;
771  else
772  {
773  ccb = cb;
774  continue;
775  }
776  }
777 
778  if ( ccb->currentIndex() == 0 )
779  {
780  cb->setCurrentIndex( 0 );
781  cb->setEnabled( false );
782  }
783  else
784  {
785  cb->blockSignals( true );
786  cb->clear();
787  cb->addItem( cb->property( "Field" ).toString() );
788 
789  // ccb = scb
790  // cb = scb + 1
791  Q_FOREACH ( const QString& txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
792  {
793  cb->addItem( txt );
794  }
795 
796  cb->setEnabled( true );
797  cb->blockSignals( false );
798 
799  ccb = cb;
800  }
801  }
802  }
803 
804  Q_FOREACH ( QComboBox* cb, mFilterComboBoxes )
805  {
806  if ( cb->currentIndex() != 0 )
807  {
808  const QString fieldName = cb->property( "Field" ).toString();
809 
810  if ( cb->currentText() == nullValue.toString() )
811  {
812  filters << QString( "\"%1\" IS NULL" ).arg( fieldName );
813  }
814  else
815  {
816  if ( mReferencedLayer->pendingFields().field( fieldName ).type() == QVariant::String )
817  {
818  filters << QString( "\"%1\" = '%2'" ).arg( fieldName ).arg( cb->currentText() );
819  }
820  else
821  {
822  filters << QString( "\"%1\" = %2" ).arg( fieldName ).arg( cb->currentText() );
823  }
824  }
825  attrs << mReferencedLayer->fieldNameIndex( fieldName );
826  }
827  }
828 
829  QString filterExpression = filters.join( " AND " );
830 
831  QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( filterExpression ).setSubsetOfAttributes( attrs ) ) );
832 
833  QgsFeature f;
834  QgsFeatureIds featureIds;
835 
836  while ( it.nextFeature( f ) )
837  {
838  featureIds << f.id();
839  }
840 
841  mFilterModel->setFilteredFeatures( featureIds );
842 }
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:51
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").
Definition: qgsexpression.h:86
Wrapper for iterator of features from vector data provider or vector layer.
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:304
const QString name() const
A rectangle specified with double values.
Definition: qgsrectangle.h:35
static const QColor DEFAULT_HIGHLIGHT_COLOR
Default highlight color.
Definition: qgis.h:300
QgsPoint layerToMapCoordinates(QgsMapLayer *theLayer, QgsPoint point) const
transform point coordinates from layer'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:304
virtual void loadLayer()
Loads the layer into the model Preferably to be called, before basing any other models on this model...
QStringList referencedColumns() const
Get list of columns referenced by the expression.
void zoomByFactor(double scaleFactor, const QgsPoint *center=0)
Zoom with the factor supplied.
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:168
void foreignKeyChanged(QVariant)
void hideButtonBox()
Hides the button box (Ok/Cancel) and enables auto-commit.
QVariant evaluate(const QgsFeature *f=NULL)
Evaluate the feature and return the result.
A groupbox that collapses/expands when toggled and can save its collapsed and checked states...
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeature.h:365
void deleteForeignKey()
unset the currently related feature
This class contains context information for attribute editor widgets.
void uniqueValues(int index, QList< QVariant > &uniqueValues, int limit=-1)
Returns unique values for column.
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)
void setExtent(const QgsRectangle &r)
Set the extent of the map canvas.
bool contains(const QgsRectangle &rect) const
return true when rectangle contains other rectangle
void setFillColor(const QColor &fillColor)
Set polygons fill color.
QgsRectangle boundingBox() const
Returns the bounding box of this feature.
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:42
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
QgsVectorLayer * referencedLayer() const
Access the referenced (parent) layer.
void refresh()
Repaints the canvas map.
QVariant foreignKey()
returns the related feature foreign key
void setFilterFields(QStringList filterFields)
Set the fields for which filter comboboxes will be created.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:119
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.
const QString displayExpression()
Get the preview expression, used to create a human readable preview string.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:105
void setCacheSubsetOfAttributes(const QgsAttributeList &attributes)
Set the subset of attributes to be cached.
void setAllowMapIdentification(bool allowMapIdentification)
bool allowMapIdentification()
determines if the widge offers the possibility to select the related feature on the map (using a dedi...
void setForeignKey(const QVariant &value)
this sets the related feature using from the foreign key
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
const QString & name() const
Get the display name of the layer.
void setOrderByValue(bool orderByValue)
Set if the widget will order the combobox entries by value.
void combineExtentWith(QgsRectangle *rect)
expand the rectangle so that covers both the original rectangle and the given rectangle ...
static QgsMessageBarItem * createMessage(const QString &text, QWidget *parent=0)
make out a widget containing a message to be displayed on the bar
void setFeature(const QgsFeature &feature)
Update all editors to correspond to a different feature.
bool setDisplayExpression(const QString expression)
bool popWidget(QgsMessageBarItem *item)
void setAction(QAction *action)
Use this to associate a QAction to this maptool.
Definition: qgsmaptool.cpp:103
virtual void showEvent(QShowEvent *e) override
QgsVectorLayer * referencingLayer() const
Access the referencing (child) layer This is the layer which has the field(s) which point to another ...
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)...
QList< int > QgsAttributeList
void setRelation(QgsRelation relation, bool allowNullValue)
QgsGeometry * centroid() const
Returns the center of mass of a geometry.
QString attributeAlias(int attributeIndex) const
Returns the alias of an attribute name or an empty string if there is no alias.
QgsVectorLayerCache * layerCache() const
Returns the layer cache this model uses as backend.
A class to represent a point.
Definition: qgspoint.h:63
bool orderByLessThan(const QgsRelationReferenceWidget::ValueRelationItem &p1, const QgsRelationReferenceWidget::ValueRelationItem &p2)
This class caches features of a given QgsVectorLayer.
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:173
void pushItem(QgsMessageBarItem *item)
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:236
void setColor(const QColor &color)
Set line/outline to color, polygon fill to color with alpha = 63.
bool chainFilters()
Determines if the filters are chained.
QPair< QVariant, QgsFeatureId > ValueRelationItem
virtual void sort(int column, Qt::SortOrder order=Qt::AscendingOrder) override
Sort by the given column using the given order.
QgsFeature referencedFeature()
return the related feature (from the referenced layer) if no feature is related, it returns an invali...
const QgsVectorLayerTools * vectorLayerTools() const
const QgsGeometry * constGeometry() const
Gets a const pointer to the geometry object associated with this feature.
Definition: qgsfeature.cpp:68
QList< FieldPair > fieldPairs() const
Returns the field pairs which form this relation The first element of each pair are the field names f...
qint64 QgsFeatureId
Definition: qgsfeature.h:31
QgsRectangle extent() const
Returns the current zoom exent of the map canvas.
void setInjectNull(bool injectNull)
If true is specified, a NULL value will be injected.
const QgsFields & pendingFields() const
returns field list in the to-be-committed state
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...
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
virtual void setFilteredFeatures(QgsFeatureIds ids)
Specify a list of features, which the filter will accept.
static double DEFAULT_HIGHLIGHT_MIN_WIDTH_MM
Default highlight line/outline minimum width in mm.
Definition: qgis.h:308
bool openFormButtonVisible()
determines the open form button is visible in the widget
void openForm()
open the form of the related feature in a new dialog
void setChainFilters(bool chainFilters)
Set if filters are chained.
A form was embedded as a widget on another form.
void setMinWidth(double width)
Set minimum line / 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:74
#define tr(sourceText)
void scale(double scaleFactor, const QgsPoint *c=0)
Scale the rectangle around its center point.