QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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( editTextUpdated( const QString & ) ) );
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 
194 
195  if ( mEmbedForm )
196  {
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  QString nullValue = QSettings().value( "qgis/nullValue", "NULL" ).toString();
280 
281  if ( mChainFilters && mFeature.isValid() && mFilterComboBoxes.count() >= mFilterFields.count() )
282  {
283  QgsFeature feature = mFeature;
284 
285  for ( int i = 0; i < mFilterFields.size(); i++ )
286  {
287  QVariant v = feature.attribute( mFilterFields[i] );
288  QString f = v.isNull() ? nullValue : v.toString();
289  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
290  }
291  }
292 
293  int i = mComboBox->findData( mFeature.id(), QgsAttributeTableModel::FeatureIdRole );
294  if ( i == -1 && mAllowNull )
295  {
296  mComboBox->setCurrentIndex( 0 );
297  }
298  else
299  {
300  mComboBox->setCurrentIndex( i );
301  }
302  }
303 
304  mRemoveFKButton->setEnabled( mIsEditable );
305  highlightFeature( mFeature );
306  updateAttributeEditorFrame( mFeature );
307  emit foreignKeyChanged( foreignKey() );
308 }
309 
311 {
312  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
313 
314  // deactivate filter comboboxes
315  if ( mChainFilters && !mFilterComboBoxes.isEmpty() )
316  {
317  QComboBox *cb = mFilterComboBoxes.first();
318  cb->setCurrentIndex( 0 );
319  disableChainedComboBoxes( cb );
320  }
321 
322  if ( mReadOnlySelector )
323  {
324  QString nullText = "";
325  if ( mAllowNull )
326  {
327  nullText = tr( "%1 (no selection)" ).arg( nullValue.toString() );
328  }
329  mLineEdit->setText( nullText );
330  mForeignKey = QVariant();
331  mFeature.setValid( false );
332  }
333  else
334  {
335  if ( mAllowNull )
336  {
337  mComboBox->setCurrentIndex( 0 );
338  }
339  else
340  {
341  mComboBox->setCurrentIndex( -1 );
342  }
343  }
344  mRemoveFKButton->setEnabled( false );
345  updateAttributeEditorFrame( QgsFeature() );
346  emit foreignKeyChanged( QVariant( QVariant::Int ) );
347 }
348 
350 {
351  QgsFeature f;
352  if ( mReferencedLayer )
353  {
354  QgsFeatureId fid;
355  if ( mReadOnlySelector )
356  {
357  fid = mFeature.id();
358  }
359  else
360  {
361  fid = mComboBox->itemData( mComboBox->currentIndex(), QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
362  }
363  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( f );
364  }
365  return f;
366 }
367 
369 {
370  if ( mReadOnlySelector )
371  {
372  whileBlocking( mLineEdit )->setText( QString() );
373  }
374  else
375  {
376  whileBlocking( mComboBox )->setCurrentIndex( -1 );
377  }
378  mRemoveFKButton->setEnabled( false );
379  updateAttributeEditorFrame( QgsFeature() );
380 }
381 
383 {
384  if ( mReadOnlySelector )
385  {
386  return mForeignKey;
387  }
388  else
389  {
390  if ( mReferencingFieldIdx < 0 || mReferencingFieldIdx >= mReferencingLayer->fields().count() )
391  {
392  return QVariant();
393  }
394  else if ( !mFeature.isValid() )
395  {
396  return QVariant( mReferencingLayer->fields().at( mReferencingFieldIdx ).type() );
397  }
398  else
399  {
400  return mFeature.attribute( mReferencedFieldIdx );
401  }
402  }
403 }
404 
406 {
407  mEditorContext = context;
408  mCanvas = canvas;
409  mMessageBar = messageBar;
410 
411  if ( mMapTool )
412  delete mMapTool;
413  mMapTool = new QgsMapToolIdentifyFeature( mCanvas );
414  mMapTool->setButton( mMapIdentificationButton );
415 }
416 
418 {
419  if ( display )
420  {
421  setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
422  mTopLayout->setAlignment( Qt::AlignTop );
423  }
424 
425  mAttributeEditorFrame->setVisible( display );
426  mEmbedForm = display;
427 }
428 
430 {
431  mChooserContainer->setHidden( readOnly );
432  mLineEdit->setVisible( readOnly );
433  mRemoveFKButton->setVisible( mAllowNull && readOnly );
434  mReadOnlySelector = readOnly;
435 }
436 
438 {
439  mHighlightFeatureButton->setVisible( allowMapIdentification );
440  mMapIdentificationButton->setVisible( allowMapIdentification );
441  mAllowMapIdentification = allowMapIdentification;
442 }
443 
445 {
446  mOrderByValue = orderByValue;
447 }
448 
450 {
451  mFilterFields = filterFields;
452 }
453 
455 {
456  mOpenFormButton->setVisible( openFormButtonVisible );
457  mOpenFormButtonVisible = openFormButtonVisible;
458 }
459 
461 {
462  mChainFilters = chainFilters;
463 }
464 
466 {
467  Q_UNUSED( e )
468 
469  mShown = true;
470 
471  init();
472 }
473 
475 {
476  if ( !mReadOnlySelector && mComboBox->count() == 0 && mReferencedLayer )
477  {
478  QApplication::setOverrideCursor( Qt::WaitCursor );
479 
480  QSet<QString> requestedAttrs;
481 
482  const int cacheSize = QSettings().value( "/QgsRelationReferenceWidget/cacheSize" ).toInt();
483  QgsVectorLayerCache* layerCache = new QgsVectorLayerCache( mReferencedLayer, cacheSize, this );
484 
485  if ( !mFilterFields.isEmpty() )
486  {
487  Q_FOREACH ( const QString& fieldName, mFilterFields )
488  {
489  QVariantList uniqueValues;
490  int idx = mReferencedLayer->fieldNameIndex( fieldName );
491 
492  if ( idx == -1 )
493  continue;
494 
495  QComboBox* cb = new QComboBox();
496  cb->setProperty( "Field", fieldName );
497  cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
498  mFilterComboBoxes << cb;
499  mReferencedLayer->uniqueValues( idx, uniqueValues );
500  cb->addItem( mReferencedLayer->attributeDisplayName( idx ) );
501  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
502  cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->fields().at( idx ).type() ) );
503 
504  qSort( uniqueValues.begin(), uniqueValues.end(), qgsVariantLessThan );
505  Q_FOREACH ( const QVariant& v, uniqueValues )
506  {
507  cb->addItem( v.toString(), v );
508  }
509 
510  connect( cb, SIGNAL( currentIndexChanged( int ) ), this, SLOT( filterChanged() ) );
511 
512  // Request this attribute for caching
513  requestedAttrs << fieldName;
514 
515  mFilterLayout->addWidget( cb );
516  }
517 
518  if ( mChainFilters )
519  {
520  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
521 
522  QgsFeature ft;
523  QgsFeatureIterator fit = layerCache->getFeatures();
524  while ( fit.nextFeature( ft ) )
525  {
526  for ( int i = 0; i < mFilterComboBoxes.count() - 1; ++i )
527  {
528  QVariant cv = ft.attribute( mFilterFields.at( i ) );
529  QVariant nv = ft.attribute( mFilterFields.at( i + 1 ) );
530  QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
531  QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
532  mFilterCache[mFilterFields[i]][cf] << nf;
533  }
534  }
535 
536  if ( !mFilterComboBoxes.isEmpty() )
537  {
538  QComboBox *cb = mFilterComboBoxes.first();
539  cb->setCurrentIndex( 0 );
540  disableChainedComboBoxes( cb );
541  }
542  }
543  }
544  else
545  {
546  mFilterContainer->hide();
547  }
548 
549  QgsExpression exp( mReferencedLayer->displayExpression() );
550 
551  requestedAttrs += exp.referencedColumns().toSet();
552  requestedAttrs << mRelation.fieldPairs().at( 0 ).second;
553 
554  QgsAttributeList attributes;
555  Q_FOREACH ( const QString& attr, requestedAttrs )
556  attributes << mReferencedLayer->fieldNameIndex( attr );
557 
558  layerCache->setCacheSubsetOfAttributes( attributes );
559  mMasterModel = new QgsAttributeTableModel( layerCache, this );
560  mMasterModel->setRequest( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( requestedAttrs.toList(), mReferencedLayer->fields() ) );
561  mFilterModel = new QgsAttributeTableFilterModel( mCanvas, mMasterModel, mMasterModel );
562  mFeatureListModel = new QgsFeatureListModel( mFilterModel, this );
563  mFeatureListModel->setDisplayExpression( mReferencedLayer->displayExpression() );
564 
565  mMasterModel->loadLayer();
566 
567  mFeatureListModel->setInjectNull( mAllowNull );
568  if ( mOrderByValue )
569  {
570  mFilterModel->sort( mReferencedLayer->displayExpression() );
571  }
572 
573  mComboBox->setModel( mFeatureListModel );
574 
575  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
576 
577  if ( mChainFilters && mFeature.isValid() )
578  {
579  for ( int i = 0; i < mFilterFields.size(); i++ )
580  {
581  QVariant v = mFeature.attribute( mFilterFields[i] );
582  QString f = v.isNull() ? nullValue.toString() : v.toString();
583  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
584  }
585  }
586 
587  if ( mFeature.isValid() )
588  {
589  mComboBox->setCurrentIndex( mComboBox->findData( mFeature.id(), QgsAttributeTableModel::FeatureIdRole ) );
590  }
591  else
592  {
593  mComboBox->setCurrentIndex( -1 );
594  }
595 
596  // Only connect after iterating, to have only one iterator on the referenced table at once
597  connect( mComboBox, SIGNAL( currentIndexChanged( int ) ), this, SLOT( comboReferenceChanged( int ) ) );
598  updateAttributeEditorFrame( mFeature );
600  }
601 }
602 
603 void QgsRelationReferenceWidget::highlightActionTriggered( QAction* action )
604 {
605  if ( action == mHighlightFeatureAction )
606  {
607  highlightFeature();
608  }
609  else if ( action == mScaleHighlightFeatureAction )
610  {
611  highlightFeature( QgsFeature(), Scale );
612  }
613  else if ( action == mPanHighlightFeatureAction )
614  {
615  highlightFeature( QgsFeature(), Pan );
616  }
617 }
618 
620 {
621  QgsFeature feat = referencedFeature();
622 
623  if ( !feat.isValid() )
624  return;
625 
627  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
628  attributeDialog.exec();
629 }
630 
631 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
632 {
633  if ( !mCanvas )
634  return;
635 
636  if ( !f.isValid() )
637  {
638  f = referencedFeature();
639  if ( !f.isValid() )
640  return;
641  }
642 
643  if ( !f.constGeometry() )
644  {
645  return;
646  }
647 
648  const QgsGeometry* geom = f.constGeometry();
649 
650  // scale or pan
651  if ( canvasExtent == Scale )
652  {
653  QgsRectangle featBBox = geom->boundingBox();
654  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
655  QgsRectangle extent = mCanvas->extent();
656  if ( !extent.contains( featBBox ) )
657  {
658  extent.combineExtentWith( featBBox );
659  extent.scale( 1.1 );
660  mCanvas->setExtent( extent );
661  mCanvas->refresh();
662  }
663  }
664  else if ( canvasExtent == Pan )
665  {
666  QgsGeometry* centroid = geom->centroid();
667  QgsPoint center = centroid->asPoint();
668  delete centroid;
669  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
670  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
671  }
672 
673  // highlight
674  deleteHighlight();
675  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
676  QSettings settings;
677  QColor color = QColor( settings.value( "/Map/highlight/color", QGis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
678  int alpha = settings.value( "/Map/highlight/colorAlpha", QGis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
679  double buffer = settings.value( "/Map/highlight/buffer", QGis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
680  double minWidth = settings.value( "/Map/highlight/minWidth", QGis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
681 
682  mHighlight->setColor( color ); // sets also fill with default alpha
683  color.setAlpha( alpha );
684  mHighlight->setFillColor( color ); // sets fill with alpha
685  mHighlight->setBuffer( buffer );
686  mHighlight->setMinWidth( minWidth );
687  mHighlight->show();
688 
689  QTimer* timer = new QTimer( this );
690  timer->setSingleShot( true );
691  connect( timer, SIGNAL( timeout() ), this, SLOT( deleteHighlight() ) );
692  timer->start( 3000 );
693 }
694 
695 void QgsRelationReferenceWidget::deleteHighlight()
696 {
697  if ( mHighlight )
698  {
699  mHighlight->hide();
700  delete mHighlight;
701  }
702  mHighlight = nullptr;
703 }
704 
706 {
707  if ( !mAllowMapIdentification || !mReferencedLayer )
708  return;
709 
710  const QgsVectorLayerTools* tools = mEditorContext.vectorLayerTools();
711  if ( !tools )
712  return;
713  if ( !mCanvas )
714  return;
715 
716  mMapTool->setLayer( mReferencedLayer );
717  mCanvas->setMapTool( mMapTool );
718 
719  mWindowWidget = window();
720 
721  mCanvas->window()->raise();
722  mCanvas->activateWindow();
723  mCanvas->setFocus();
724 
725  connect( mMapTool, SIGNAL( featureIdentified( QgsFeature ) ), this, SLOT( featureIdentified( const QgsFeature ) ) );
726  connect( mMapTool, SIGNAL( deactivated() ), this, SLOT( mapToolDeactivated() ) );
727 
728  if ( mMessageBar )
729  {
730  QString title = tr( "Relation %1 for %2." ).arg( mRelationName, mReferencingLayer->name() );
731  QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
732  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
733  mMessageBar->pushItem( mMessageBarItem );
734  }
735 }
736 
737 void QgsRelationReferenceWidget::comboReferenceChanged( int index )
738 {
740  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( mFeature );
741  highlightFeature( mFeature );
742  updateAttributeEditorFrame( mFeature );
743  emit foreignKeyChanged( mFeature.attribute( mReferencedFieldIdx ) );
744 }
745 
746 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature& feature )
747 {
748  mOpenFormButton->setEnabled( feature.isValid() );
749  // Check if we're running with an embedded frame we need to update
750  if ( mAttributeEditorFrame && mReferencedAttributeForm )
751  {
752  mReferencedAttributeForm->setFeature( feature );
753  }
754 }
755 
757 {
758  return mAllowAddFeatures;
759 }
760 
762 {
763  mAllowAddFeatures = allowAddFeatures;
764  updateAddEntryButton();
765 }
766 
767 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature& feature )
768 {
769  if ( mReadOnlySelector )
770  {
771  QgsExpression expr( mReferencedLayer->displayExpression() );
772  QgsExpressionContext context;
775  << QgsExpressionContextUtils::layerScope( mReferencedLayer );
776  context.setFeature( feature );
777  QString title = expr.evaluate( &context ).toString();
778  if ( expr.hasEvalError() )
779  {
780  title = feature.attribute( mReferencedFieldIdx ).toString();
781  }
782  mLineEdit->setText( title );
783  mForeignKey = feature.attribute( mReferencedFieldIdx );
784  mFeature = feature;
785  }
786  else
787  {
788  mComboBox->setCurrentIndex( mComboBox->findData( feature.id(), QgsAttributeTableModel::FeatureIdRole ) );
789  mFeature = feature;
790  }
791 
792  mRemoveFKButton->setEnabled( mIsEditable );
793  highlightFeature( feature );
794  updateAttributeEditorFrame( feature );
795  emit foreignKeyChanged( foreignKey() );
796 
797  unsetMapTool();
798 }
799 
800 void QgsRelationReferenceWidget::unsetMapTool()
801 {
802  // deactivate map tool if activated
803  if ( mCanvas && mMapTool )
804  {
805  /* this will call mapToolDeactivated */
806  mCanvas->unsetMapTool( mMapTool );
807  }
808 }
809 
810 void QgsRelationReferenceWidget::mapToolDeactivated()
811 {
812  if ( mWindowWidget )
813  {
814  mWindowWidget->raise();
815  mWindowWidget->activateWindow();
816  }
817 
818  if ( mMessageBar && mMessageBarItem )
819  {
820  mMessageBar->popWidget( mMessageBarItem );
821  }
822  mMessageBarItem = nullptr;
823 }
824 
825 void QgsRelationReferenceWidget::filterChanged()
826 {
827  QVariant nullValue = QSettings().value( "qgis/nullValue", "NULL" );
828 
829  QMap<QString, QString> filters;
830  QgsAttributeList attrs;
831 
832  QComboBox* scb = qobject_cast<QComboBox*>( sender() );
833 
834  Q_ASSERT( scb );
835 
836  QgsFeature f;
837  QgsFeatureIds featureIds;
838  QString filterExpression;
839 
840  // comboboxes have to be disabled before building filters
841  if ( mChainFilters )
842  disableChainedComboBoxes( scb );
843 
844  // build filters
845  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
846  {
847  if ( cb->currentIndex() != 0 )
848  {
849  const QString fieldName = cb->property( "Field" ).toString();
850 
851  if ( cb->currentText() == nullValue.toString() )
852  {
853  filters[fieldName] = QString( "\"%1\" IS NULL" ).arg( fieldName );
854  }
855  else
856  {
857  filters[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
858  }
859  attrs << mReferencedLayer->fieldNameIndex( fieldName );
860  }
861  }
862 
863  bool filtered = false;
864  if ( mChainFilters )
865  {
866  QComboBox* ccb = nullptr;
867  Q_FOREACH ( QComboBox* cb, mFilterComboBoxes )
868  {
869  if ( !ccb )
870  {
871  if ( cb == scb )
872  ccb = cb;
873 
874  continue;
875  }
876 
877  if ( ccb->currentIndex() != 0 )
878  {
879  const QString fieldName = cb->property( "Field" ).toString();
880  filtered = true;
881 
882  cb->blockSignals( true );
883  cb->clear();
884  cb->addItem( cb->property( "FieldAlias" ).toString() );
885 
886  // ccb = scb
887  // cb = scb + 1
888  QStringList texts;
889  Q_FOREACH ( const QString& txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
890  {
891  QMap<QString, QString> filtersAttrs = filters;
892  filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt );
893  QStringList vals = filtersAttrs.values();
894  QString expression = vals.join( QString( " AND " ) );
895 
896  QgsAttributeList subset = attrs;
897  subset << mReferencedLayer->fieldNameIndex( fieldName );
898 
899  QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
900 
901  bool found = false;
902  while ( it.nextFeature( f ) )
903  {
904  if ( !featureIds.contains( f.id() ) )
905  featureIds << f.id();
906 
907  found = true;
908  }
909 
910  // item is only provided if at least 1 feature exists
911  if ( found )
912  texts << txt;
913  }
914 
915  texts.sort();
916  cb->addItems( texts );
917 
918  cb->setEnabled( true );
919  cb->blockSignals( false );
920 
921  ccb = cb;
922  }
923  }
924  }
925 
926  if ( !mChainFilters || ( mChainFilters && !filtered ) )
927  {
928  QStringList vals = filters.values();
929  filterExpression = vals.join( QString( " AND " ) );
930 
932  if ( !filterExpression.isEmpty() )
933  req.setFilterExpression( filterExpression );
934 
935  QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( req ) );
936 
937  while ( it.nextFeature( f ) )
938  {
939  featureIds << f.id();
940  }
941  }
942 
943  mFilterModel->setFilteredFeatures( featureIds );
944 
945  if ( mChainFilters && mComboBox->count() > 0 )
946  {
947  if ( scb->currentIndex() == 0 )
948  {
949  mComboBox->setCurrentIndex( 0 );
950  }
951  else if ( mComboBox->count() > 1 )
952  {
953  mComboBox->setCurrentIndex( 1 );
954  }
955  }
956 }
957 
958 void QgsRelationReferenceWidget::addEntry()
959 {
960  QgsFeature f( mReferencedLayer->fields() );
961  QgsAttributeMap attributes;
962 
963  // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
964  if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
965  {
966  int fieldIdx = mReferencedLayer->fieldNameIndex( mReferencedLayer->displayExpression() );
967 
968  if ( fieldIdx != -1 )
969  {
970  attributes.insert( fieldIdx, mComboBox->currentText() );
971  }
972  }
973 
974  if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, QgsGeometry(), &f ) )
975  {
976  int i = mComboBox->findData( f.id(), QgsAttributeTableModel::FeatureIdRole );
977  mComboBox->setCurrentIndex( i );
978  mAddEntryButton->setEnabled( false );
979  }
980 }
981 
982 void QgsRelationReferenceWidget::updateAddEntryButton()
983 {
984  mAddEntryButton->setVisible( mAllowAddFeatures );
985  mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
986 }
987 
988 void QgsRelationReferenceWidget::disableChainedComboBoxes( const QComboBox *scb )
989 {
990  QComboBox *ccb = nullptr;
991  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
992  {
993  if ( !ccb )
994  {
995  if ( cb == scb )
996  {
997  ccb = cb;
998  }
999 
1000  continue;
1001  }
1002 
1003  cb->setCurrentIndex( 0 );
1004  if ( ccb->currentIndex() == 0 )
1005  {
1006  cb->setEnabled( false );
1007  }
1008 
1009  ccb = cb;
1010  }
1011 }
1012 
1013 void QgsRelationReferenceWidget::editTextUpdated( const QString &text )
1014 {
1015  updateAddEntryButton();
1016 
1017  // allow to raise an invalid constraint on NULL values if necessary
1018  // and when the combobox is updated manually from the keyboard
1019  if ( text.isEmpty() && mAllowNull )
1020  mComboBox->setCurrentIndex( 0 );
1021 }
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.
QList< T > values() const
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.
QObject * sender() const
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.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
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)
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...
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.
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)
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
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()
bool isEmpty() const
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()
T & first()
void setCheckable(bool)
void hide()
void setRelation(const QgsRelation &relation, bool allowNullValue)
void setSizePolicy(QSizePolicy)
void addWidget(QWidget *w)
static QString createFieldEqualityExpression(const QString &fieldName, const QVariant &value)
Create an expression allowing to evaluate if a field is equal to a value.
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)
bool contains(const T &value) const
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
bool setProperty(const char *name, const QVariant &value)
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)
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
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
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)