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