QGIS API Documentation  2.99.0-Master (716ff6c)
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, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::openForm );
169  connect( mHighlightFeatureButton, &QToolButton::triggered, this, &QgsRelationReferenceWidget::highlightActionTriggered );
170  connect( mMapIdentificationButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::mapIdentification );
171  connect( mRemoveFKButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::deleteForeignKey );
172  connect( mAddEntryButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::addEntry );
173  connect( mComboBox, &QComboBox::editTextChanged, this, &QgsRelationReferenceWidget::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, &QgsVectorLayer::editingStarted, this, &QgsRelationReferenceWidget::updateAddEntryButton );
211  connect( mReferencedLayer, &QgsVectorLayer::editingStopped, this, &QgsRelationReferenceWidget::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, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::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 displayExpression( mReferencedLayer->displayExpression() );
519 
520  requestedAttrs += displayExpression.referencedColumns();
521  requestedAttrs << mRelation.fieldPairs().at( 0 ).second;
522 
523  Q_FOREACH ( const QgsConditionalStyle &style, mReferencedLayer->conditionalStyles()->rowStyles() )
524  {
525  QgsExpression exp( style.rule() );
526  requestedAttrs += exp.referencedColumns();
527  }
528 
529  if ( displayExpression.isField() )
530  {
531  Q_FOREACH ( const QgsConditionalStyle &style, mReferencedLayer->conditionalStyles()->fieldStyles( *displayExpression.referencedColumns().constBegin() ) )
532  {
533  QgsExpression exp( style.rule() );
534  requestedAttrs += exp.referencedColumns();
535  }
536  }
537 
538  QgsAttributeList attributes;
539  Q_FOREACH ( const QString &attr, requestedAttrs )
540  attributes << mReferencedLayer->fields().lookupField( attr );
541 
542  layerCache->setCacheSubsetOfAttributes( attributes );
543  mMasterModel = new QgsAttributeTableModel( layerCache );
544  mMasterModel->setRequest( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( requestedAttrs, mReferencedLayer->fields() ) );
545  mFilterModel = new QgsAttributeTableFilterModel( mCanvas, mMasterModel, mMasterModel );
546  mFeatureListModel = new QgsFeatureListModel( mFilterModel, this );
547  mFeatureListModel->setDisplayExpression( mReferencedLayer->displayExpression() );
548 
549  mMasterModel->loadLayer();
550 
551  mFeatureListModel->setInjectNull( mAllowNull );
552  if ( mOrderByValue )
553  {
554  mFilterModel->sort( mReferencedLayer->displayExpression() );
555  }
556 
557  mComboBox->setModel( mFeatureListModel );
558 
559  delete mComboBox->completer();
560  QCompleter *completer = new QCompleter( mComboBox->model(), mComboBox );
561  completer->setModel( mComboBox->model() );
562  completer->setFilterMode( Qt::MatchContains );
563  completer->setCaseSensitivity( Qt::CaseInsensitive );
564  mComboBox->setCompleter( completer );
565 
566 
567  QVariant nullValue = QgsApplication::nullRepresentation();
568 
569  if ( mChainFilters && mFeature.isValid() )
570  {
571  for ( int i = 0; i < mFilterFields.size(); i++ )
572  {
573  QVariant v = mFeature.attribute( mFilterFields[i] );
574  QString f = v.isNull() ? nullValue.toString() : v.toString();
575  mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
576  }
577  }
578 
579  QVariant featId = mFeature.isValid() ? mFeature.id() : QVariant( QVariant::Int );
580  mComboBox->setCurrentIndex( mComboBox->findData( featId, QgsAttributeTableModel::FeatureIdRole ) );
581 
582  // Only connect after iterating, to have only one iterator on the referenced table at once
583  connect( mComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::comboReferenceChanged );
584  updateAttributeEditorFrame( mFeature );
585  QApplication::restoreOverrideCursor();
586  }
587 }
588 
589 void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
590 {
591  if ( action == mHighlightFeatureAction )
592  {
593  highlightFeature();
594  }
595  else if ( action == mScaleHighlightFeatureAction )
596  {
597  highlightFeature( QgsFeature(), Scale );
598  }
599  else if ( action == mPanHighlightFeatureAction )
600  {
601  highlightFeature( QgsFeature(), Pan );
602  }
603 }
604 
606 {
607  QgsFeature feat = referencedFeature();
608 
609  if ( !feat.isValid() )
610  return;
611 
613  QgsAttributeDialog attributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
614  attributeDialog.exec();
615 }
616 
617 void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
618 {
619  if ( !mCanvas )
620  return;
621 
622  if ( !f.isValid() )
623  {
624  f = referencedFeature();
625  if ( !f.isValid() )
626  return;
627  }
628 
629  if ( !f.hasGeometry() )
630  {
631  return;
632  }
633 
634  QgsGeometry geom = f.geometry();
635 
636  // scale or pan
637  if ( canvasExtent == Scale )
638  {
639  QgsRectangle featBBox = geom.boundingBox();
640  featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
641  QgsRectangle extent = mCanvas->extent();
642  if ( !extent.contains( featBBox ) )
643  {
644  extent.combineExtentWith( featBBox );
645  extent.scale( 1.1 );
646  mCanvas->setExtent( extent );
647  mCanvas->refresh();
648  }
649  }
650  else if ( canvasExtent == Pan )
651  {
652  QgsGeometry centroid = geom.centroid();
653  QgsPoint center = centroid.asPoint();
654  center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
655  mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
656  }
657 
658  // highlight
659  deleteHighlight();
660  mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
661  QgsSettings settings;
662  QColor color = QColor( settings.value( QStringLiteral( "Map/highlight/color" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.name() ).toString() );
663  int alpha = settings.value( QStringLiteral( "Map/highlight/colorAlpha" ), Qgis::DEFAULT_HIGHLIGHT_COLOR.alpha() ).toInt();
664  double buffer = settings.value( QStringLiteral( "Map/highlight/buffer" ), Qgis::DEFAULT_HIGHLIGHT_BUFFER_MM ).toDouble();
665  double minWidth = settings.value( QStringLiteral( "Map/highlight/minWidth" ), Qgis::DEFAULT_HIGHLIGHT_MIN_WIDTH_MM ).toDouble();
666 
667  mHighlight->setColor( color ); // sets also fill with default alpha
668  color.setAlpha( alpha );
669  mHighlight->setFillColor( color ); // sets fill with alpha
670  mHighlight->setBuffer( buffer );
671  mHighlight->setMinWidth( minWidth );
672  mHighlight->show();
673 
674  QTimer *timer = new QTimer( this );
675  timer->setSingleShot( true );
676  connect( timer, &QTimer::timeout, this, &QgsRelationReferenceWidget::deleteHighlight );
677  timer->start( 3000 );
678 }
679 
680 void QgsRelationReferenceWidget::deleteHighlight()
681 {
682  if ( mHighlight )
683  {
684  mHighlight->hide();
685  delete mHighlight;
686  }
687  mHighlight = nullptr;
688 }
689 
691 {
692  if ( !mAllowMapIdentification || !mReferencedLayer )
693  return;
694 
695  const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
696  if ( !tools )
697  return;
698  if ( !mCanvas )
699  return;
700 
701  mMapTool->setLayer( mReferencedLayer );
702  mCanvas->setMapTool( mMapTool );
703 
704  mWindowWidget = window();
705 
706  mCanvas->window()->raise();
707  mCanvas->activateWindow();
708  mCanvas->setFocus();
709 
710  connect( mMapTool, static_cast<void ( QgsMapToolIdentifyFeature::* )( const QgsFeature & )>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
711  connect( mMapTool, &QgsMapTool::deactivated, this, &QgsRelationReferenceWidget::mapToolDeactivated );
712 
713  if ( mMessageBar )
714  {
715  QString title = tr( "Relation %1 for %2." ).arg( mRelationName, mReferencingLayer->name() );
716  QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
717  mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
718  mMessageBar->pushItem( mMessageBarItem );
719  }
720 }
721 
722 void QgsRelationReferenceWidget::comboReferenceChanged( int index )
723 {
724  QgsFeatureId fid = mComboBox->itemData( index, QgsAttributeTableModel::FeatureIdRole ).value<QgsFeatureId>();
725  mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterFid( fid ) ).nextFeature( mFeature );
726  highlightFeature( mFeature );
727  updateAttributeEditorFrame( mFeature );
728  emit foreignKeyChanged( mFeature.attribute( mReferencedFieldIdx ) );
729 }
730 
731 void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature &feature )
732 {
733  mOpenFormButton->setEnabled( feature.isValid() );
734  // Check if we're running with an embedded frame we need to update
735  if ( mAttributeEditorFrame && mReferencedAttributeForm )
736  {
737  mReferencedAttributeForm->setFeature( feature );
738  }
739 }
740 
742 {
743  return mAllowAddFeatures;
744 }
745 
747 {
748  mAllowAddFeatures = allowAddFeatures;
749  updateAddEntryButton();
750 }
751 
752 void QgsRelationReferenceWidget::featureIdentified( const QgsFeature &feature )
753 {
754  if ( mReadOnlySelector )
755  {
756  QgsExpression expr( mReferencedLayer->displayExpression() );
758  context.setFeature( feature );
759  QString title = expr.evaluate( &context ).toString();
760  if ( expr.hasEvalError() )
761  {
762  title = feature.attribute( mReferencedFieldIdx ).toString();
763  }
764  mLineEdit->setText( title );
765  mForeignKey = feature.attribute( mReferencedFieldIdx );
766  mFeature = feature;
767  }
768  else
769  {
770  mComboBox->setCurrentIndex( mComboBox->findData( feature.id(), QgsAttributeTableModel::FeatureIdRole ) );
771  mFeature = feature;
772  }
773 
774  mRemoveFKButton->setEnabled( mIsEditable );
775  highlightFeature( feature );
776  updateAttributeEditorFrame( feature );
777  emit foreignKeyChanged( foreignKey() );
778 
779  unsetMapTool();
780 }
781 
782 void QgsRelationReferenceWidget::unsetMapTool()
783 {
784  // deactivate map tool if activated
785  if ( mCanvas && mMapTool )
786  {
787  /* this will call mapToolDeactivated */
788  mCanvas->unsetMapTool( mMapTool );
789  }
790 }
791 
792 void QgsRelationReferenceWidget::mapToolDeactivated()
793 {
794  if ( mWindowWidget )
795  {
796  mWindowWidget->raise();
797  mWindowWidget->activateWindow();
798  }
799 
800  if ( mMessageBar && mMessageBarItem )
801  {
802  mMessageBar->popWidget( mMessageBarItem );
803  }
804  mMessageBarItem = nullptr;
805 }
806 
807 void QgsRelationReferenceWidget::filterChanged()
808 {
809  QVariant nullValue = QgsApplication::nullRepresentation();
810 
811  QStringList filters;
812  QgsAttributeList attrs;
813 
814  QComboBox *scb = qobject_cast<QComboBox *>( sender() );
815 
816  Q_ASSERT( scb );
817 
818  if ( mChainFilters )
819  {
820  QComboBox *ccb = nullptr;
821  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
822  {
823  if ( !ccb )
824  {
825  if ( cb == scb )
826  ccb = cb;
827 
828  continue;
829  }
830 
831  if ( ccb->currentIndex() == 0 )
832  {
833  cb->setCurrentIndex( 0 );
834  cb->setEnabled( false );
835  }
836  else
837  {
838  cb->blockSignals( true );
839  cb->clear();
840  cb->addItem( cb->property( "FieldAlias" ).toString() );
841 
842  // ccb = scb
843  // cb = scb + 1
844  QStringList texts;
845  Q_FOREACH ( const QString &txt, mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] )
846  {
847  texts << txt;
848  }
849  texts.sort();
850  cb->addItems( texts );
851 
852  cb->setEnabled( true );
853  cb->blockSignals( false );
854 
855  ccb = cb;
856  }
857  }
858  }
859 
860  Q_FOREACH ( QComboBox *cb, mFilterComboBoxes )
861  {
862  if ( cb->currentIndex() != 0 )
863  {
864  const QString fieldName = cb->property( "Field" ).toString();
865 
866  if ( cb->currentText() == nullValue.toString() )
867  {
868  filters << QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
869  }
870  else
871  {
872  if ( mReferencedLayer->fields().field( fieldName ).type() == QVariant::String )
873  {
874  filters << QStringLiteral( "\"%1\" = '%2'" ).arg( fieldName, cb->currentText() );
875  }
876  else
877  {
878  filters << QStringLiteral( "\"%1\" = %2" ).arg( fieldName, cb->currentText() );
879  }
880  }
881  attrs << mReferencedLayer->fields().lookupField( fieldName );
882  }
883  }
884 
885  QString filterExpression = filters.join( QStringLiteral( " AND " ) );
886 
887  QgsFeatureIterator it( mMasterModel->layerCache()->getFeatures( QgsFeatureRequest().setFilterExpression( filterExpression ).setSubsetOfAttributes( attrs ) ) );
888 
889  QgsFeature f;
890  QgsFeatureIds featureIds;
891 
892  while ( it.nextFeature( f ) )
893  {
894  featureIds << f.id();
895  }
896 
897  mFilterModel->setFilteredFeatures( featureIds );
898 }
899 
900 void QgsRelationReferenceWidget::addEntry()
901 {
902  QgsFeature f( mReferencedLayer->fields() );
903  QgsAttributeMap attributes;
904 
905  // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
906  if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
907  {
908  int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
909 
910  if ( fieldIdx != -1 )
911  {
912  attributes.insert( fieldIdx, mComboBox->currentText() );
913  }
914  }
915 
916  if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, QgsGeometry(), &f ) )
917  {
918  int i = mComboBox->findData( f.id(), QgsAttributeTableModel::FeatureIdRole );
919  mComboBox->setCurrentIndex( i );
920  mAddEntryButton->setEnabled( false );
921  }
922 }
923 
924 void QgsRelationReferenceWidget::updateAddEntryButton()
925 {
926  mAddEntryButton->setVisible( mAllowAddFeatures );
927  mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
928 }
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:176
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:44
QgsFeatureId id
Definition: qgsfeature.h:70
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:38
virtual void loadLayer()
Loads the layer into the model Preferably to be called, before using this model as source for any oth...
QgsPoint asPoint() const
Return contents of the geometry as a point if wkbType is WKBPoint, otherwise returns [0...
void setLayer(QgsVectorLayer *vl)
change the layer used by the map tool to identify
QgsFeature referencedFeature() const
return the related feature (from the referenced layer) if no feature is related, it returns an invali...
bool chainFilters() const
Determines if the filters are chained.
bool setDisplayExpression(const QString &expression)
This class is a composition of two QSettings instances:
Definition: qgssettings.h:54
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:517
void deleteForeignKey()
unset the currently related feature
void foreignKeyChanged(const QVariant &)
This class contains context information for attribute editor widgets.
void setOpenFormButtonVisible(bool openFormButtonVisible)
QList< QgsConditionalStyle > fieldStyles(const QString &fieldName)
Returns the conditional styles set for the field UI properties.
QgsVectorLayer referencingLayer
Definition: qgsrelation.h:42
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:61
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:190
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:136
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:130
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.
QgsConditionalLayerStyles * conditionalStyles() const
Return the conditional styles that are set for 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:79
void setOrderByValue(bool orderByValue)
Set if the widget will order the combobox entries by value.
QList< QgsConditionalStyle > rowStyles()
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...
Conditional styling for a rule.
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.
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:39
QgsRectangle extent() const
Returns the current zoom extent of the map canvas.
void editingStopped()
Is emitted, when edited changes successfully have been written to the data provider.
A class for highlight features on the map.
Definition: qgshighlight.h:39
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:43
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:101
void setRelation(const QgsRelation &relation, bool allowNullValue)
void editingStarted()
Is emitted, when editing on this layer has started.
A class to represent a point.
Definition: qgspoint.h:37
QString displayExpression
QList< QgsRelation::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:181
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:178
void deactivated()
signal emitted once the map tool is deactivated
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:45
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 featureIdentified(const QgsFeature &)
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=NoSection) const
Returns the value for setting key.
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:37
static const double DEFAULT_HIGHLIGHT_MIN_WIDTH_MM
Default highlight line/stroke minimum width in mm.
Definition: qgis.h:109
QString name
Definition: qgsmaplayer.h:57
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: qgsattributes.h:57
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:255
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 rule() const
The condition rule set for the style.
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:83