QGIS API Documentation 3.99.0-Master (26c88405ac0)
Loading...
Searching...
No Matches
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 "qgsapplication.h"
19#include "qgsattributedialog.h"
20#include "qgsattributeform.h"
22#include "qgsexpression.h"
23#include "qgsfeatureiterator.h"
25#include "qgsfields.h"
26#include "qgsgeometry.h"
27#include "qgshighlight.h"
28#include "qgsmapcanvas.h"
31#include "qgsmessagebar.h"
32#include "qgsvectorlayer.h"
33#include "qgsvectorlayerutils.h"
34
35#include <QCompleter>
36#include <QDialog>
37#include <QHBoxLayout>
38#include <QPushButton>
39#include <QTimer>
40
41#include "moc_qgsrelationreferencewidget.cpp"
42
43bool qVariantListIsNull( const QVariantList &list )
44{
45 if ( list.isEmpty() )
46 return true;
47
48 for ( int i = 0; i < list.size(); ++i )
49 {
50 if ( !QgsVariantUtils::isNull( list.at( i ) ) )
51 return false;
52 }
53 return true;
54}
55
56
58 : QWidget( parent )
59{
60 mTopLayout = new QVBoxLayout( this );
61 mTopLayout->setContentsMargins( 0, 0, 0, 0 );
62
63 setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::Fixed );
64
65 setLayout( mTopLayout );
66
67 QHBoxLayout *editLayout = new QHBoxLayout();
68 editLayout->setContentsMargins( 0, 0, 0, 0 );
69 editLayout->setSpacing( 2 );
70
71 // Prepare the container and layout for the filter comboboxes
72 mChooserContainer = new QWidget;
73 editLayout->addWidget( mChooserContainer );
74 QHBoxLayout *chooserLayout = new QHBoxLayout;
75 chooserLayout->setContentsMargins( 0, 0, 0, 0 );
76 mFilterLayout = new QHBoxLayout;
77 mFilterLayout->setContentsMargins( 0, 0, 0, 0 );
78 mFilterContainer = new QWidget;
79 mFilterContainer->setLayout( mFilterLayout );
80 mChooserContainer->setLayout( chooserLayout );
81 chooserLayout->addWidget( mFilterContainer );
82
83 mComboBox = new QgsFeatureListComboBox();
84 mChooserContainer->layout()->addWidget( mComboBox );
85
86 // open form button
87 mOpenFormButton = new QToolButton();
88 mOpenFormButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPropertyItem.svg" ) ) );
89 mOpenFormButton->setText( tr( "Open Related Feature Form" ) );
90 editLayout->addWidget( mOpenFormButton );
91
92 mAddEntryButton = new QToolButton();
93 mAddEntryButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAdd.svg" ) ) );
94 mAddEntryButton->setText( tr( "Add New Entry" ) );
95 editLayout->addWidget( mAddEntryButton );
96
97 // highlight button
98 mHighlightFeatureButton = new QToolButton( this );
99 mHighlightFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
100 mHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHighlightFeature.svg" ) ), tr( "Highlight feature" ), this );
101 mScaleHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleHighlightFeature.svg" ) ), tr( "Scale and highlight feature" ), this );
102 mPanHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPanHighlightFeature.svg" ) ), tr( "Pan and highlight feature" ), this );
103 mHighlightFeatureButton->addAction( mHighlightFeatureAction );
104 mHighlightFeatureButton->addAction( mScaleHighlightFeatureAction );
105 mHighlightFeatureButton->addAction( mPanHighlightFeatureAction );
106 mHighlightFeatureButton->setDefaultAction( mHighlightFeatureAction );
107 editLayout->addWidget( mHighlightFeatureButton );
108
109 // map identification button
110 mMapIdentificationButton = new QToolButton( this );
111 mMapIdentificationButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMapIdentification.svg" ) ) );
112 mMapIdentificationButton->setText( tr( "Select on Map" ) );
113 mMapIdentificationButton->setCheckable( true );
114 editLayout->addWidget( mMapIdentificationButton );
115
116 // remove foreign key button
117 mRemoveFKButton = new QToolButton( this );
118 mRemoveFKButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemove.svg" ) ) );
119 mRemoveFKButton->setText( tr( "No Selection" ) );
120 editLayout->addWidget( mRemoveFKButton );
121
122 // add line to top layout
123 mTopLayout->addLayout( editLayout );
124
125 // embed form
126 mAttributeEditorFrame = new QgsCollapsibleGroupBox( this );
127 mAttributeEditorLayout = new QVBoxLayout( mAttributeEditorFrame );
128 mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
129 mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
130 mTopLayout->addWidget( mAttributeEditorFrame );
131
132 // invalid label
133 mInvalidLabel = new QLabel( tr( "The relation is not valid. Please make sure your relation definitions are OK." ) );
134 mInvalidLabel->setWordWrap( true );
135 QFont font = mInvalidLabel->font();
136 font.setItalic( true );
137 mInvalidLabel->setStyleSheet( QStringLiteral( "QLabel { color: red; } " ) );
138 mInvalidLabel->setFont( font );
139 mTopLayout->addWidget( mInvalidLabel );
140
141 // default mode is combobox, no geometric relation and no embed form
142 mMapIdentificationButton->hide();
143 mHighlightFeatureButton->hide();
144 mAttributeEditorFrame->hide();
145 mInvalidLabel->hide();
146 mAddEntryButton->hide();
147
148 // connect buttons
149 connect( mOpenFormButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::openForm );
150 connect( mHighlightFeatureButton, &QToolButton::triggered, this, &QgsRelationReferenceWidget::highlightActionTriggered );
151 connect( mMapIdentificationButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::mapIdentification );
152 connect( mRemoveFKButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::deleteForeignKeys );
153 connect( mAddEntryButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::addEntry );
154 connect( mComboBox, &QComboBox::editTextChanged, this, &QgsRelationReferenceWidget::updateAddEntryButton );
155}
156
158{
159 deleteHighlight();
160 unsetMapTool();
161}
162
164{
165 mAllowNull = allowNullValue;
166 mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
167
168 if ( relation.isValid() )
169 {
170 mReferencedLayerId = relation.referencedLayerId();
171 mReferencedLayerName = relation.referencedLayer()->name();
172 setReferencedLayerDataSource( relation.referencedLayer()->publicSource() );
173 mReferencedLayerProviderKey = relation.referencedLayer()->providerType();
174 mInvalidLabel->hide();
175
176 mRelation = relation;
177 mReferencingLayer = relation.referencingLayer();
178 mReferencedLayer = relation.referencedLayer();
179
180 const QList<QgsRelation::FieldPair> fieldPairs = relation.fieldPairs();
181 for ( const QgsRelation::FieldPair &fieldPair : fieldPairs )
182 {
183 mReferencedFields << fieldPair.referencedField();
184 }
185 if ( mComboBox )
186 {
187 mComboBox->setAllowNull( mAllowNull );
188 mComboBox->setSourceLayer( mReferencedLayer );
189 mComboBox->setIdentifierFields( mReferencedFields );
190 mComboBox->setFilterExpression( mFilterExpression );
191 mComboBox->setFetchLimit( mFetchLimit );
192 mComboBox->setOrderExpression( mOrderExpression );
193 mComboBox->setSortOrder( mSortOrder );
194 }
195 mAttributeEditorFrame->setObjectName( QStringLiteral( "referencing/" ) + relation.name() );
196
197 if ( mEmbedForm )
198 {
200 mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
201 mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
202 mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
203 }
204
205 connect( mReferencedLayer, &QgsVectorLayer::editingStarted, this, &QgsRelationReferenceWidget::updateAddEntryButton );
206 connect( mReferencedLayer, &QgsVectorLayer::editingStopped, this, &QgsRelationReferenceWidget::updateAddEntryButton );
207 updateAddEntryButton();
208 }
209 else
210 {
211 mInvalidLabel->show();
212 }
213
214 if ( mShown && isVisible() )
215 {
216 init();
217 }
218}
219
221{
222 if ( !editable )
223 {
224 unsetMapTool();
225 }
226
227 mFilterContainer->setEnabled( editable );
228 mComboBox->setEnabled( editable && !mReadOnlySelector );
229 mComboBox->setEditable( true );
230 mMapIdentificationButton->setEnabled( editable );
231 mRemoveFKButton->setEnabled( editable );
232 mIsEditable = editable;
233}
234
235void QgsRelationReferenceWidget::setForeignKey( const QVariant &value )
236{
237 setForeignKeys( QVariantList() << value );
238}
239
240void QgsRelationReferenceWidget::setForeignKeys( const QVariantList &values )
241{
242 if ( values.isEmpty() )
243 {
244 return;
245 }
246 if ( qVariantListIsNull( values ) )
247 {
249 return;
250 }
251
252 if ( !mReferencedLayer )
253 return;
254
255 mComboBox->setIdentifierValues( values );
256
257 if ( mEmbedForm || mChainFilters )
258 {
259 QgsFeatureRequest request = mComboBox->currentFeatureRequest();
260 mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
261 }
262 if ( mChainFilters )
263 {
264 QVariant nullValue = QgsApplication::nullRepresentation();
265 const int count = std::min( mFilterComboBoxes.size(), mFilterFields.size() );
266 for ( int i = 0; i < count; i++ )
267 {
268 QVariant v = mFeature.attribute( mFilterFields[i] );
269 QString f = QgsVariantUtils::isNull( v ) ? nullValue.toString() : v.toString();
270 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
271 }
272 }
273
274 mRemoveFKButton->setEnabled( mIsEditable );
275 highlightFeature( mFeature ); // TODO : make this async
276 updateAttributeEditorFrame( mFeature );
277
278 emitForeignKeysChanged( foreignKeys() );
279}
280
282{
283 // deactivate filter comboboxes
284 if ( mChainFilters && !mFilterComboBoxes.isEmpty() )
285 {
286 QComboBox *cb = mFilterComboBoxes.first();
287 cb->setCurrentIndex( 0 );
288 disableChainedComboBoxes( cb );
289 }
290
291 mComboBox->setIdentifierValuesToNull();
292 mRemoveFKButton->setEnabled( false );
293 updateAttributeEditorFrame( QgsFeature() );
294
295 emitForeignKeysChanged( foreignKeys() );
296}
297
299{
300 if ( !mEmbedForm )
301 // if there is no embedded form, everything is fine anyway
302 return true;
303
304 return mReferencedAttributeForm->save();
305}
306
308{
309 QgsFeature f;
310 if ( mReferencedLayer )
311 {
312 mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( f );
313 }
314 return f;
315}
316
318{
319 whileBlocking( mComboBox )->setIdentifierValuesToNull();
320 mRemoveFKButton->setEnabled( false );
321 updateAttributeEditorFrame( QgsFeature() );
322}
323
325{
326 QVariantList fkeys;
327 if ( fkeys.isEmpty() )
328 return QgsVariantUtils::createNullVariant( QMetaType::Type::Int );
329 else
330 return fkeys.at( 0 );
331}
332
334{
335 return mComboBox->identifierValues();
336}
337
339{
340 mEditorContext = context;
341 mCanvas = canvas;
342 mMessageBar = messageBar;
343
344 mMapToolIdentify.reset( new QgsMapToolIdentifyFeature( mCanvas ) );
345 mMapToolIdentify->setButton( mMapIdentificationButton );
346
347 if ( mEditorContext.cadDockWidget() )
348 {
349 mMapToolDigitize.reset( new QgsMapToolDigitizeFeature( mCanvas, mEditorContext.cadDockWidget() ) );
350 mMapToolDigitize->setButton( mAddEntryButton );
351 updateAddEntryButton();
352 }
353}
354
356{
357 if ( display )
358 {
359 setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
360 mTopLayout->setAlignment( Qt::AlignTop );
361 }
362
363 mAttributeEditorFrame->setVisible( display );
364 mEmbedForm = display;
365}
366
368{
369 mComboBox->setEnabled( !readOnly );
370 mRemoveFKButton->setVisible( mAllowNull && readOnly );
371 mReadOnlySelector = readOnly;
372}
373
375{
376 mHighlightFeatureButton->setVisible( allowMapIdentification );
377 mMapIdentificationButton->setVisible( allowMapIdentification );
378 mAllowMapIdentification = allowMapIdentification;
379}
380
381void QgsRelationReferenceWidget::setFilterFields( const QStringList &filterFields )
382{
383 mFilterFields = filterFields;
384}
385
387{
388 mOpenFormButton->setVisible( openFormButtonVisible );
389 mOpenFormButtonVisible = openFormButtonVisible;
390}
391
396
397void QgsRelationReferenceWidget::setFilterExpression( const QString &expression )
398{
399 mFilterExpression = expression;
400}
401
403{
404 Q_UNUSED( e )
405
406 mShown = true;
407 if ( !mInitialized )
408 init();
409}
410
412{
413 if ( mReferencedLayer )
414 {
415 QApplication::setOverrideCursor( Qt::WaitCursor );
416
417 QSet<QString> requestedAttrs;
418
419 if ( !mFilterFields.isEmpty() )
420 {
421 for ( const QString &fieldName : std::as_const( mFilterFields ) )
422 {
423 int idx = mReferencedLayer->fields().lookupField( fieldName );
424
425 if ( idx == -1 )
426 continue;
427
428 QComboBox *cb = new QComboBox();
429 cb->setProperty( "Field", fieldName );
430 cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
431 mFilterComboBoxes << cb;
432 QVariantList uniqueValues = qgis::setToList( mReferencedLayer->uniqueValues( idx ) );
433 cb->addItem( mReferencedLayer->attributeDisplayName( idx ) );
434 QVariant nullValue = QgsApplication::nullRepresentation();
435 cb->addItem( nullValue.toString(), QgsVariantUtils::createNullVariant( mReferencedLayer->fields().at( idx ).type() ) );
436
437 std::sort( uniqueValues.begin(), uniqueValues.end(), qgsVariantLessThan );
438 const auto constUniqueValues = uniqueValues;
439 for ( const QVariant &v : constUniqueValues )
440 {
441 cb->addItem( v.toString(), v );
442 }
443
444 connect( cb, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::filterChanged );
445
446 // Request this attribute for caching
447 requestedAttrs << fieldName;
448
449 mFilterLayout->addWidget( cb );
450 }
451
452 if ( mChainFilters )
453 {
454 QVariant nullValue = QgsApplication::nullRepresentation();
455
456 QgsFeature ft;
457 QgsFeatureIterator fit = mFilterExpression.isEmpty()
458 ? mReferencedLayer->getFeatures()
459 : mReferencedLayer->getFeatures( mFilterExpression );
460 while ( fit.nextFeature( ft ) )
461 {
462 const int count = std::min( mFilterComboBoxes.count(), mFilterFields.count() );
463 for ( int i = 0; i < count - 1; i++ )
464 {
465 QVariant cv = ft.attribute( mFilterFields.at( i ) );
466 QVariant nv = ft.attribute( mFilterFields.at( i + 1 ) );
467 QString cf = QgsVariantUtils::isNull( cv ) ? nullValue.toString() : cv.toString();
468 QString nf = QgsVariantUtils::isNull( nv ) ? nullValue.toString() : nv.toString();
469 mFilterCache[mFilterFields[i]][cf] << nf;
470 }
471 }
472
473 if ( !mFilterComboBoxes.isEmpty() )
474 {
475 QComboBox *cb = mFilterComboBoxes.first();
476 cb->setCurrentIndex( 0 );
477 disableChainedComboBoxes( cb );
478 }
479 }
480 }
481 else
482 {
483 mFilterContainer->hide();
484 }
485
486 mComboBox->setSourceLayer( mReferencedLayer );
487 mComboBox->setDisplayExpression( mReferencedLayer->displayExpression() );
488 mComboBox->setAllowNull( mAllowNull );
489 mComboBox->setIdentifierFields( mReferencedFields );
490 mComboBox->setFetchLimit( mFetchLimit );
491 mComboBox->setOrderExpression( mOrderExpression );
492 mComboBox->setSortOrder( mSortOrder );
493
494 if ( !mFilterExpression.isEmpty() )
495 mComboBox->setFilterExpression( mFilterExpression );
496
497 QVariant nullValue = QgsApplication::nullRepresentation();
498
499 if ( mChainFilters && mFeature.isValid() )
500 {
501 for ( int i = 0; i < mFilterFields.size(); i++ )
502 {
503 QVariant v = mFeature.attribute( mFilterFields[i] );
504 QString f = QgsVariantUtils::isNull( v ) ? nullValue.toString() : v.toString();
505 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
506 }
507 }
508
509 // Only connect after iterating, to have only one iterator on the referenced table at once
510 connect( mComboBox, &QgsFeatureListComboBox::currentFeatureChanged, this, &QgsRelationReferenceWidget::comboReferenceChanged );
511 // To avoid wrongly signaling a foreign key change, handle model feature found state following feature gathering separately
512 connect( mComboBox, &QgsFeatureListComboBox::currentFeatureFoundChanged, this, &QgsRelationReferenceWidget::comboReferenceFoundChanged );
513
514 QApplication::restoreOverrideCursor();
515
516 mInitialized = true;
517 }
518}
519
520void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
521{
522 if ( action == mHighlightFeatureAction )
523 {
524 highlightFeature();
525 }
526 else if ( action == mScaleHighlightFeatureAction )
527 {
528 highlightFeature( QgsFeature(), Scale );
529 }
530 else if ( action == mPanHighlightFeatureAction )
531 {
532 highlightFeature( QgsFeature(), Pan );
533 }
534}
535
537{
539
540 if ( !feat.isValid() )
541 return;
542
544 QgsAttributeDialog *attributeDialog = new QgsAttributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
545 attributeDialog->show();
546}
547
548void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
549{
550 if ( !mCanvas )
551 return;
552
553 if ( !f.isValid() )
554 {
555 f = referencedFeature();
556 if ( !f.isValid() )
557 return;
558 }
559
560 if ( !f.hasGeometry() )
561 {
562 return;
563 }
564
565 QgsGeometry geom = f.geometry();
566
567 // scale or pan
568 if ( canvasExtent == Scale )
569 {
570 QgsRectangle featBBox = geom.boundingBox();
571 featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
572 QgsRectangle extent = mCanvas->extent();
573 if ( !extent.contains( featBBox ) )
574 {
575 extent.combineExtentWith( featBBox );
576 extent.scale( 1.1 );
577 mCanvas->setExtent( extent, true );
578 mCanvas->refresh();
579 }
580 }
581 else if ( canvasExtent == Pan )
582 {
583 QgsGeometry centroid = geom.centroid();
584 QgsPointXY center = centroid.asPoint();
585 center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
586 mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
587 }
588
589 // highlight
590 deleteHighlight();
591 mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
592 mHighlight->applyDefaultStyle();
593 mHighlight->show();
594
595 QTimer *timer = new QTimer( this );
596 timer->setSingleShot( true );
597 connect( timer, &QTimer::timeout, this, &QgsRelationReferenceWidget::deleteHighlight );
598 timer->start( 3000 );
599}
600
601void QgsRelationReferenceWidget::deleteHighlight()
602{
603 if ( mHighlight )
604 {
605 mHighlight->hide();
606 delete mHighlight;
607 }
608 mHighlight = nullptr;
609}
610
612{
613 if ( !mAllowMapIdentification || !mReferencedLayer )
614 return;
615
616 const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
617 if ( !tools )
618 return;
619 if ( !mCanvas )
620 return;
621
622 mMapToolIdentify->setLayer( mReferencedLayer );
623 setMapTool( mMapToolIdentify );
624
625 connect( mMapToolIdentify, qOverload<const QgsFeature &>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
626
627 if ( mMessageBar )
628 {
629 QString title = tr( "Relation %1 for %2." ).arg( mRelation.name(), mReferencingLayer->name() );
630 QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
631 mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
632 mMessageBar->pushItem( mMessageBarItem );
633 }
634}
635
636void QgsRelationReferenceWidget::comboReferenceChanged()
637{
638 mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( mFeature );
639 highlightFeature( mFeature );
640 updateAttributeEditorFrame( mFeature );
641
642 emitForeignKeysChanged( mComboBox->identifierValues() );
643}
644
645void QgsRelationReferenceWidget::comboReferenceFoundChanged( bool )
646{
647 mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( mFeature );
648 highlightFeature( mFeature );
649 updateAttributeEditorFrame( mFeature );
650}
651
652void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature &feature )
653{
654 mOpenFormButton->setEnabled( feature.isValid() );
655 // Check if we're running with an embedded frame we need to update
656 if ( mAttributeEditorFrame && mReferencedAttributeForm )
657 {
658 mReferencedAttributeForm->setFeature( feature );
659 }
660}
661
663{
664 return mAllowAddFeatures;
665}
666
668{
669 mAllowAddFeatures = allowAddFeatures;
670 updateAddEntryButton();
671}
672
674{
675 return mRelation;
676}
677
678void QgsRelationReferenceWidget::featureIdentified( const QgsFeature &feature )
679{
680 mComboBox->setCurrentFeature( feature );
681 mFeature = feature;
682
683 mRemoveFKButton->setEnabled( mIsEditable );
684 highlightFeature( feature );
685 updateAttributeEditorFrame( feature );
686 emitForeignKeysChanged( foreignKeys(), true );
687
688 unsetMapTool();
689}
690
691void QgsRelationReferenceWidget::setMapTool( QgsMapTool *mapTool )
692{
693 mCurrentMapTool = mapTool;
694 mCanvas->setMapTool( mapTool );
695
696 mWindowWidget = window();
697
698 mCanvas->window()->raise();
699 mCanvas->activateWindow();
700 mCanvas->setFocus();
701 connect( mapTool, &QgsMapTool::deactivated, this, &QgsRelationReferenceWidget::mapToolDeactivated );
702}
703
704void QgsRelationReferenceWidget::unsetMapTool()
705{
706 // deactivate map tools if activated
707 if ( mCurrentMapTool )
708 {
709 /* this will call mapToolDeactivated */
710 mCanvas->unsetMapTool( mCurrentMapTool );
711
712 if ( mCurrentMapTool == mMapToolDigitize )
713 {
714 disconnect( mCanvas, &QgsMapCanvas::keyPressed, this, &QgsRelationReferenceWidget::onKeyPressed );
715 disconnect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationReferenceWidget::entryAdded );
716 }
717 else
718 {
719 disconnect( mMapToolIdentify, qOverload<const QgsFeature &>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
720 }
721 }
722}
723
724void QgsRelationReferenceWidget::onKeyPressed( QKeyEvent *e )
725{
726 if ( e->key() == Qt::Key_Escape )
727 {
728 unsetMapTool();
729 }
730}
731
732void QgsRelationReferenceWidget::mapToolDeactivated()
733{
734 if ( mWindowWidget )
735 {
736 mWindowWidget->raise();
737 mWindowWidget->activateWindow();
738 }
739
740 if ( mMessageBar && mMessageBarItem )
741 {
742 mMessageBar->popWidget( mMessageBarItem );
743 }
744 mMessageBarItem = nullptr;
745}
746
747void QgsRelationReferenceWidget::filterChanged()
748{
749 QVariant nullValue = QgsApplication::nullRepresentation();
750
751 QMap<QString, QString> filters;
752 QgsAttributeList attrs;
753
754 QComboBox *scb = qobject_cast<QComboBox *>( sender() );
755
756 Q_ASSERT( scb );
757
758 QgsFeature f;
759 QgsFeatureIds featureIds;
760 QString filterExpression = mFilterExpression;
761
762 // wrap the expression with parentheses as it might contain `OR`
763 if ( !filterExpression.isEmpty() )
764 filterExpression = QStringLiteral( " ( %1 ) " ).arg( filterExpression );
765
766 // comboboxes have to be disabled before building filters
767 if ( mChainFilters )
768 disableChainedComboBoxes( scb );
769
770 // build filters
771 const auto constMFilterComboBoxes = mFilterComboBoxes;
772 for ( QComboBox *cb : constMFilterComboBoxes )
773 {
774 if ( cb->currentIndex() != 0 )
775 {
776 const QString fieldName = cb->property( "Field" ).toString();
777
778 if ( cb->currentText() == nullValue.toString() )
779 {
780 filters[fieldName] = QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
781 }
782 else
783 {
784 filters[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
785 }
786 attrs << mReferencedLayer->fields().lookupField( fieldName );
787 }
788 }
789
790 if ( mChainFilters )
791 {
792 QComboBox *ccb = nullptr;
793 const auto constMFilterComboBoxes = mFilterComboBoxes;
794 for ( QComboBox *cb : constMFilterComboBoxes )
795 {
796 if ( !ccb )
797 {
798 if ( cb == scb )
799 ccb = cb;
800
801 continue;
802 }
803
804 if ( ccb->currentIndex() != 0 )
805 {
806 const QString fieldName = cb->property( "Field" ).toString();
807
808 cb->blockSignals( true );
809 cb->clear();
810 cb->addItem( cb->property( "FieldAlias" ).toString() );
811
812 // ccb = scb
813 // cb = scb + 1
814 QStringList texts;
815 const auto txts { mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] };
816 for ( const QString &txt : txts )
817 {
818 QMap<QString, QString> filtersAttrs = filters;
819 filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt );
820 QgsAttributeList subset = attrs;
821
822 QString expression = filterExpression;
823 if ( !expression.isEmpty() && !filtersAttrs.isEmpty() )
824 expression += QLatin1String( " AND " );
825
826 expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral( " ( " );
827 expression += qgsMapJoinValues( filtersAttrs, QLatin1String( " AND " ) );
828 expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral( " ) " );
829
830 subset << mReferencedLayer->fields().lookupField( fieldName );
831
832 QgsFeatureIterator it( mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
833
834 bool found = false;
835 while ( it.nextFeature( f ) )
836 {
837 if ( !featureIds.contains( f.id() ) )
838 featureIds << f.id();
839
840 found = true;
841 }
842
843 // item is only provided if at least 1 feature exists
844 if ( found )
845 texts << txt;
846 }
847
848 texts.sort();
849 cb->addItems( texts );
850
851 cb->setEnabled( true );
852 cb->blockSignals( false );
853
854 ccb = cb;
855 }
856 }
857 }
858
859 if ( !filterExpression.isEmpty() && !filters.isEmpty() )
860 filterExpression += QLatin1String( " AND " );
861
862 filterExpression += filters.isEmpty() ? QString() : QStringLiteral( " ( " );
863 filterExpression += qgsMapJoinValues( filters, QLatin1String( " AND " ) );
864 filterExpression += filters.isEmpty() ? QString() : QStringLiteral( " ) " );
865
866 mComboBox->setFilterExpression( filterExpression );
867}
868
869void QgsRelationReferenceWidget::addEntry()
870{
871 if ( !mReferencedLayer )
872 return;
873
874 const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
875 if ( !tools )
876 return;
877 if ( !mCanvas )
878 return;
879
880 // no geometry, skip the digitizing
881 if ( mReferencedLayer->geometryType() == Qgis::GeometryType::Unknown || mReferencedLayer->geometryType() == Qgis::GeometryType::Null )
882 {
883 QgsFeature f( mReferencedLayer->fields() );
884 entryAdded( f );
885 return;
886 }
887
888 mMapToolDigitize->setLayer( mReferencedLayer );
889 setMapTool( mMapToolDigitize );
890
891 connect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationReferenceWidget::entryAdded );
892 connect( mCanvas, &QgsMapCanvas::keyPressed, this, &QgsRelationReferenceWidget::onKeyPressed );
893
894 if ( mMessageBar )
895 {
896 QString title = tr( "Relation %1 for %2." ).arg( mRelation.name(), mReferencingLayer->name() );
897
898 QString displayString = QgsVectorLayerUtils::getFeatureDisplayString( mReferencingLayer, mComboBox->formFeature() );
899 QString msg = tr( "Link feature to %1 \"%2\" : Digitize the geometry for the new feature on layer %3. Press &lt;ESC&gt; to cancel." )
900 .arg( mReferencingLayer->name(), displayString, mReferencedLayer->name() );
901 mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
902 mMessageBar->pushItem( mMessageBarItem );
903 }
904}
905
906void QgsRelationReferenceWidget::entryAdded( const QgsFeature &feat )
907{
908 QgsFeature f( feat );
909 QgsAttributeMap attributes;
910
911 // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
912 if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
913 {
914 int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
915
916 if ( fieldIdx != -1 )
917 {
918 attributes.insert( fieldIdx, mComboBox->currentText() );
919 }
920 }
921
922 if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, f.geometry(), &f, this, false, true ) )
923 {
924 QVariantList attrs;
925 for ( const QString &fieldName : std::as_const( mReferencedFields ) )
926 attrs << f.attribute( fieldName );
927
928 setForeignKeys( attrs );
929
930 mAddEntryButton->setEnabled( false );
931 }
932
933 unsetMapTool();
934}
935
936void QgsRelationReferenceWidget::updateAddEntryButton()
937{
938 mAddEntryButton->setVisible( mAllowAddFeatures && mMapToolDigitize );
939 mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
940}
941
942void QgsRelationReferenceWidget::disableChainedComboBoxes( const QComboBox *scb )
943{
944 QComboBox *ccb = nullptr;
945 const auto constMFilterComboBoxes = mFilterComboBoxes;
946 for ( QComboBox *cb : constMFilterComboBoxes )
947 {
948 if ( !ccb )
949 {
950 if ( cb == scb )
951 {
952 ccb = cb;
953 }
954
955 continue;
956 }
957
958 cb->setCurrentIndex( 0 );
959 if ( ccb->currentIndex() == 0 )
960 {
961 cb->setEnabled( false );
962 }
963
964 ccb = cb;
965 }
966}
967
968void QgsRelationReferenceWidget::emitForeignKeysChanged( const QVariantList &foreignKeys, bool force )
969{
970 if ( foreignKeys == mForeignKeys && force == false && qVariantListIsNull( foreignKeys ) == qVariantListIsNull( mForeignKeys ) )
971 return;
972
973 mForeignKeys = foreignKeys;
975 emit foreignKeyChanged( foreignKeys.at( 0 ) );
978}
979
981{
982 return mReferencedLayerName;
983}
984
985void QgsRelationReferenceWidget::setReferencedLayerName( const QString &relationLayerName )
986{
987 mReferencedLayerName = relationLayerName;
988}
989
991{
992 return mReferencedLayerId;
993}
994
995void QgsRelationReferenceWidget::setReferencedLayerId( const QString &relationLayerId )
996{
997 mReferencedLayerId = relationLayerId;
998}
999
1001{
1002 return mReferencedLayerProviderKey;
1003}
1004
1005void QgsRelationReferenceWidget::setReferencedLayerProviderKey( const QString &relationProviderKey )
1006{
1007 mReferencedLayerProviderKey = relationProviderKey;
1008}
1009
1011{
1012 return mReferencedLayerDataSource;
1013}
1014
1015void QgsRelationReferenceWidget::setReferencedLayerDataSource( const QString &relationDataSource )
1016{
1017 const QgsPathResolver resolver { QgsProject::instance()->pathResolver() };
1018 mReferencedLayerDataSource = resolver.writePath( relationDataSource );
1019}
1020
1022{
1023 mComboBox->setFormFeature( formFeature );
1024}
1025
1027{
1028 mComboBox->setParentFormFeature( parentFormFeature );
1029}
@ Unknown
Unknown types.
Definition qgis.h:362
@ Null
No geometry.
Definition qgis.h:363
static QString nullRepresentation()
Returns the string used to represent the value NULL throughout QGIS.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
An attribute table dialog for a vector layer.
void show()
Show the dialog non-blocking. Reparents this dialog to be a child of the dialog form.
Contains context information for attribute editor widgets.
@ Single
When showing a single feature (e.g. district information when looking at the form of a house).
@ Embed
A form was embedded as a widget on another form.
@ StandaloneDialog
A form was opened as a new dialog.
The attribute form widget for vector layer features.
A groupbox that collapses/expands when toggled and can save its collapsed and checked states.
static QString createFieldEqualityExpression(const QString &fieldName, const QVariant &value, QMetaType::Type fieldType=QMetaType::Type::UnknownType)
Create an expression allowing to evaluate if a field is equal to a value.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
This offers a combobox with autocompleter that allows selecting features from a layer.
QgsFeatureRequest currentFeatureRequest() const
Shorthand for getting a feature request to query the currently selected feature.
void setCurrentFeature(const QgsFeature &feature)
Sets the current index by using the given feature.
void currentFeatureFoundChanged(bool found)
Emitted when the feature picker model changes its feature found state.
void currentFeatureChanged()
Emitted when the current feature changes.
Wraps a request for features to a vector layer (or directly its vector data provider).
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
bool hasGeometry() const
Returns true if the feature has an associated geometry.
bool isValid() const
Returns the validity of this feature.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Map canvas is a class for displaying all GIS data types on a canvas.
void keyPressed(QKeyEvent *e)
Emit key press event.
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
void editingStarted()
Emitted when editing on this layer has started.
This tool digitizes geometry of new point/line/polygon features on already existing vector layers.
void digitizingCompleted(const QgsFeature &feature)
Emitted whenever the digitizing has been successfully completed.
A map tool to identify a feature on a chosen layer.
void featureIdentified(const QgsFeature &feature)
Emitted when a feature has been identified.
Abstract base class for all map tools.
Definition qgsmaptool.h:72
void deactivated()
Emitted when the map tool is deactivated.
A bar for displaying non-blocking messages to the user.
static QgsMessageBarItem * createMessage(const QString &text, QWidget *parent=nullptr)
Creates message bar item widget containing a message text to be displayed on the bar.
Resolves relative paths into absolute paths and vice versa.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
static QgsProject * instance()
Returns the QgsProject singleton instance.
QgsPathResolver pathResolver() const
Returns path resolver object with considering whether the project uses absolute or relative paths and...
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
void showEvent(QShowEvent *e) override
bool allowMapIdentification() const
determines if the widget offers the possibility to select the related feature on the map (using a ded...
void setFilterExpression(const QString &filterExpression)
If not empty, will be used as filter expression.
QString filterExpression() const
Returns the currently set filter expression.
void setReferencedLayerProviderKey(const QString &referencedLayerProviderKey)
Set the data provider key of the referenced layer to referencedLayerProviderKey.
QString referencedLayerDataSource() const
Returns the public data source of the referenced layer.
bool allowAddFeatures() const
Determines if a button for adding new features should be shown.
Q_DECL_DEPRECATED void setForeignKey(const QVariant &value)
this sets the related feature using from the foreign key
QString referencedLayerProviderKey() const
Returns the data provider key of the referenced layer.
void setChainFilters(bool chainFilters)
Set if filters are chained.
QString referencedLayerId() const
Returns the id of the referenced layer.
void setEditorContext(const QgsAttributeEditorContext &context, QgsMapCanvas *canvas, QgsMessageBar *messageBar)
Sets the editor context.
void setReferencedLayerName(const QString &referencedLayerName)
Set the name of the referenced layer to referencedLayerName.
bool saveReferencedAttributeForm()
Trigger save of the embedded referenced attribute form.
void showIndeterminateState()
Sets the widget to display in an indeterminate "mixed value" state.
void openForm()
open the form of the related feature in a new dialog
void setReferencedLayerDataSource(const QString &referencedLayerDataSource)
Set the public data source of the referenced layer to referencedLayerDataSource.
void setParentFormFeature(const QgsFeature &parentFormFeature)
Set the current parent form feature.
void setFilterFields(const QStringList &filterFields)
Sets the fields for which filter comboboxes will be created.
void setAllowMapIdentification(bool allowMapIdentification)
Q_DECL_DEPRECATED void foreignKeyChanged(const QVariant &key)
Emitted when the foreign key changed.
QgsRelation relation() const
Returns the current relation, which might be invalid.
void setReferencedLayerId(const QString &referencedLayerId)
Set the id of the referenced layer to referencedLayerId.
bool chainFilters() const
Determines if the filters are chained.
QVariantList foreignKeys() const
returns the related feature foreign key
void setForeignKeys(const QVariantList &values)
Sets the related feature using the foreign keys.
Q_DECL_DEPRECATED QVariant foreignKey() const
returns the related feature foreign key
void foreignKeysChanged(const QVariantList &keys)
Emitted when the foreign keys changed.
void setRelation(const QgsRelation &relation, bool allowNullValue)
void setOpenFormButtonVisible(bool openFormButtonVisible)
QgsFeature referencedFeature() const
Returns the related feature (from the referenced layer) if no feature is related, it returns an inval...
void mapIdentification()
activate the map tool to select a new related feature on the map
void setAllowAddFeatures(bool allowAddFeatures)
Determines if a button for adding new features should be shown.
void deleteForeignKeys()
unset the currently related feature
QString referencedLayerName() const
Returns the name of the referenced layer.
void setFormFeature(const QgsFeature &formFeature)
Set the current form feature (from the referencing layer).
Defines a relation between matching fields of the two involved tables of a relation.
Definition qgsrelation.h:72
Represents a relationship between two vector layers.
Definition qgsrelation.h:42
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
static QVariant createNullVariant(QMetaType::Type metaType)
Helper method to properly create a null QVariant from a metaType Returns the created QVariant.
Used to handle basic editing operations on vector layers.
static QString getFeatureDisplayString(const QgsVectorLayer *layer, const QgsFeature &feature)
Returns a descriptive string for a feature, suitable for displaying to the user.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
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:588
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7170
QString qgsMapJoinValues(const QMap< Key, Value > &map, const QString &separator)
Joins all the map values into a single string with each element separated by the given separator.
Definition qgis.h:6681
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7169
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6511
QMap< int, QVariant > QgsAttributeMap
QSet< QgsFeatureId > QgsFeatureIds
QList< int > QgsAttributeList
Definition qgsfield.h:28
bool qVariantListIsNull(const QVariantList &list)