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