QGIS API Documentation 3.29.0-Master (ade4f0cf0f)
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"
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"
42#include "qgsfeatureiterator.h"
46#include "qgsvectorlayerutils.h"
47
48
49bool qVariantListIsNull( const QVariantList &list )
50{
51 if ( list.isEmpty() )
52 return true;
53
54 for ( int i = 0; i < list.size(); ++i )
55 {
56 if ( !QgsVariantUtils::isNull( list.at( i ) ) )
57 return false;
58 }
59 return true;
60}
61
62
64 : QWidget( parent )
65{
66 mTopLayout = new QVBoxLayout( this );
67 mTopLayout->setContentsMargins( 0, 0, 0, 0 );
68
69 setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::Fixed );
70
71 setLayout( mTopLayout );
72
73 QHBoxLayout *editLayout = new QHBoxLayout();
74 editLayout->setContentsMargins( 0, 0, 0, 0 );
75 editLayout->setSpacing( 2 );
76
77 // Prepare the container and layout for the filter comboboxes
78 mChooserContainer = new QWidget;
79 editLayout->addWidget( mChooserContainer );
80 QHBoxLayout *chooserLayout = new QHBoxLayout;
81 chooserLayout->setContentsMargins( 0, 0, 0, 0 );
82 mFilterLayout = new QHBoxLayout;
83 mFilterLayout->setContentsMargins( 0, 0, 0, 0 );
84 mFilterContainer = new QWidget;
85 mFilterContainer->setLayout( mFilterLayout );
86 mChooserContainer->setLayout( chooserLayout );
87 chooserLayout->addWidget( mFilterContainer );
88
89 mComboBox = new QgsFeatureListComboBox();
90 mChooserContainer->layout()->addWidget( mComboBox );
91
92 // open form button
93 mOpenFormButton = new QToolButton();
94 mOpenFormButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPropertyItem.svg" ) ) );
95 mOpenFormButton->setText( tr( "Open Related Feature Form" ) );
96 editLayout->addWidget( mOpenFormButton );
97
98 mAddEntryButton = new QToolButton();
99 mAddEntryButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAdd.svg" ) ) );
100 mAddEntryButton->setText( tr( "Add New Entry" ) );
101 editLayout->addWidget( mAddEntryButton );
102
103 // highlight button
104 mHighlightFeatureButton = new QToolButton( this );
105 mHighlightFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
106 mHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHighlightFeature.svg" ) ), tr( "Highlight feature" ), this );
107 mScaleHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionScaleHighlightFeature.svg" ) ), tr( "Scale and highlight feature" ), this );
108 mPanHighlightFeatureAction = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPanHighlightFeature.svg" ) ), tr( "Pan and highlight feature" ), this );
109 mHighlightFeatureButton->addAction( mHighlightFeatureAction );
110 mHighlightFeatureButton->addAction( mScaleHighlightFeatureAction );
111 mHighlightFeatureButton->addAction( mPanHighlightFeatureAction );
112 mHighlightFeatureButton->setDefaultAction( mHighlightFeatureAction );
113 editLayout->addWidget( mHighlightFeatureButton );
114
115 // map identification button
116 mMapIdentificationButton = new QToolButton( this );
117 mMapIdentificationButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionMapIdentification.svg" ) ) );
118 mMapIdentificationButton->setText( tr( "Select on Map" ) );
119 mMapIdentificationButton->setCheckable( true );
120 editLayout->addWidget( mMapIdentificationButton );
121
122 // remove foreign key button
123 mRemoveFKButton = new QToolButton( this );
124 mRemoveFKButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemove.svg" ) ) );
125 mRemoveFKButton->setText( tr( "No Selection" ) );
126 editLayout->addWidget( mRemoveFKButton );
127
128 // add line to top layout
129 mTopLayout->addLayout( editLayout );
130
131 // embed form
132 mAttributeEditorFrame = new QgsCollapsibleGroupBox( this );
133 mAttributeEditorLayout = new QVBoxLayout( mAttributeEditorFrame );
134 mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
135 mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
136 mTopLayout->addWidget( mAttributeEditorFrame );
137
138 // invalid label
139 mInvalidLabel = new QLabel( tr( "The relation is not valid. Please make sure your relation definitions are OK." ) );
140 mInvalidLabel->setWordWrap( true );
141 QFont font = mInvalidLabel->font();
142 font.setItalic( true );
143 mInvalidLabel->setStyleSheet( QStringLiteral( "QLabel { color: red; } " ) );
144 mInvalidLabel->setFont( font );
145 mTopLayout->addWidget( mInvalidLabel );
146
147 // default mode is combobox, no geometric relation and no embed form
148 mMapIdentificationButton->hide();
149 mHighlightFeatureButton->hide();
150 mAttributeEditorFrame->hide();
151 mInvalidLabel->hide();
152 mAddEntryButton->hide();
153
154 // connect buttons
155 connect( mOpenFormButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::openForm );
156 connect( mHighlightFeatureButton, &QToolButton::triggered, this, &QgsRelationReferenceWidget::highlightActionTriggered );
157 connect( mMapIdentificationButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::mapIdentification );
158 connect( mRemoveFKButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::deleteForeignKeys );
159 connect( mAddEntryButton, &QAbstractButton::clicked, this, &QgsRelationReferenceWidget::addEntry );
160 connect( mComboBox, &QComboBox::editTextChanged, this, &QgsRelationReferenceWidget::updateAddEntryButton );
161}
162
164{
165 deleteHighlight();
166 unsetMapTool();
167}
168
169void QgsRelationReferenceWidget::setRelation( const QgsRelation &relation, bool allowNullValue )
170{
171 mAllowNull = allowNullValue;
172 mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
173
174 if ( relation.isValid() )
175 {
176 mReferencedLayerId = relation.referencedLayerId();
177 mReferencedLayerName = relation.referencedLayer()->name();
179 mReferencedLayerProviderKey = relation.referencedLayer()->providerType();
180 mInvalidLabel->hide();
181
182 mRelation = relation;
183 mReferencingLayer = relation.referencingLayer();
184 mReferencedLayer = relation.referencedLayer();
185
186 const QList<QgsRelation::FieldPair> fieldPairs = relation.fieldPairs();
187 for ( const QgsRelation::FieldPair &fieldPair : fieldPairs )
188 {
189 mReferencedFields << fieldPair.referencedField();
190 }
191 if ( mComboBox )
192 {
193 mComboBox->setAllowNull( mAllowNull );
194 mComboBox->setSourceLayer( mReferencedLayer );
195 mComboBox->setIdentifierFields( mReferencedFields );
196 mComboBox->setFilterExpression( mFilterExpression );
197 }
198 mAttributeEditorFrame->setObjectName( QStringLiteral( "referencing/" ) + relation.name() );
199
200 if ( mEmbedForm )
201 {
203 mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
204 mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
205 mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
206 }
207
208 connect( mReferencedLayer, &QgsVectorLayer::editingStarted, this, &QgsRelationReferenceWidget::updateAddEntryButton );
209 connect( mReferencedLayer, &QgsVectorLayer::editingStopped, this, &QgsRelationReferenceWidget::updateAddEntryButton );
210 updateAddEntryButton();
211 }
212 else
213 {
214 mInvalidLabel->show();
215 }
216
217 if ( mShown && isVisible() )
218 {
219 init();
220 }
221}
222
224{
225 if ( !editable )
226 {
227 unsetMapTool();
228 }
229
230 mFilterContainer->setEnabled( editable );
231 mComboBox->setEnabled( editable && !mReadOnlySelector );
232 mComboBox->setEditable( true );
233 mMapIdentificationButton->setEnabled( editable );
234 mRemoveFKButton->setEnabled( editable );
235 mIsEditable = editable;
236}
237
238void QgsRelationReferenceWidget::setForeignKey( const QVariant &value )
239{
240 setForeignKeys( QVariantList() << value );
241}
242
243void QgsRelationReferenceWidget::setForeignKeys( const QVariantList &values )
244{
245 if ( values.isEmpty() )
246 {
247 return;
248 }
249 if ( qVariantListIsNull( values ) )
250 {
252 return;
253 }
254
255 if ( !mReferencedLayer )
256 return;
257
258 mComboBox->setIdentifierValues( values );
259
260 if ( mEmbedForm || mChainFilters )
261 {
262 QgsFeatureRequest request = mComboBox->currentFeatureRequest();
263 mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
264 }
265 if ( mChainFilters )
266 {
267 QVariant nullValue = QgsApplication::nullRepresentation();
268 const int count = std::min( mFilterComboBoxes.size(), mFilterFields.size() );
269 for ( int i = 0; i < count; i++ )
270 {
271 QVariant v = mFeature.attribute( mFilterFields[i] );
272 QString f = QgsVariantUtils::isNull( v ) ? nullValue.toString() : v.toString();
273 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
274 }
275 }
276
277 mRemoveFKButton->setEnabled( mIsEditable );
278 highlightFeature( mFeature ); // TODO : make this async
279 updateAttributeEditorFrame( mFeature );
280
281 emitForeignKeysChanged( foreignKeys() );
282}
283
285{
286 // deactivate filter comboboxes
287 if ( mChainFilters && !mFilterComboBoxes.isEmpty() )
288 {
289 QComboBox *cb = mFilterComboBoxes.first();
290 cb->setCurrentIndex( 0 );
291 disableChainedComboBoxes( cb );
292 }
293
294 mComboBox->setIdentifierValuesToNull();
295 mRemoveFKButton->setEnabled( false );
296 updateAttributeEditorFrame( QgsFeature() );
297
298 emitForeignKeysChanged( foreignKeys() );
299}
300
302{
303 QgsFeature f;
304 if ( mReferencedLayer )
305 {
306 mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( f );
307 }
308 return f;
309}
310
312{
313 whileBlocking( mComboBox )->setIdentifierValuesToNull();
314 mRemoveFKButton->setEnabled( false );
315 updateAttributeEditorFrame( QgsFeature() );
316}
317
319{
320 QVariantList fkeys;
321 if ( fkeys.isEmpty() )
322 return QVariant( QVariant::Int );
323 else
324 return fkeys.at( 0 );
325}
326
328{
329 return mComboBox->identifierValues();
330}
331
333{
334 mEditorContext = context;
335 mCanvas = canvas;
336 mMessageBar = messageBar;
337
338 mMapToolIdentify.reset( new QgsMapToolIdentifyFeature( mCanvas ) );
339 mMapToolIdentify->setButton( mMapIdentificationButton );
340
341 if ( mEditorContext.cadDockWidget() )
342 {
343 mMapToolDigitize.reset( new QgsMapToolDigitizeFeature( mCanvas, mEditorContext.cadDockWidget() ) );
344 mMapToolDigitize->setButton( mAddEntryButton );
345 updateAddEntryButton();
346 }
347}
348
350{
351 if ( display )
352 {
353 setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
354 mTopLayout->setAlignment( Qt::AlignTop );
355 }
356
357 mAttributeEditorFrame->setVisible( display );
358 mEmbedForm = display;
359}
360
362{
363 mComboBox->setEnabled( !readOnly );
364 mRemoveFKButton->setVisible( mAllowNull && readOnly );
365 mReadOnlySelector = readOnly;
366}
367
369{
370 mHighlightFeatureButton->setVisible( allowMapIdentification );
371 mMapIdentificationButton->setVisible( allowMapIdentification );
372 mAllowMapIdentification = allowMapIdentification;
373}
374
376{
377 mOrderByValue = orderByValue;
378}
379
380void QgsRelationReferenceWidget::setFilterFields( const QStringList &filterFields )
381{
382 mFilterFields = filterFields;
383}
384
386{
387 mOpenFormButton->setVisible( openFormButtonVisible );
388 mOpenFormButtonVisible = openFormButtonVisible;
389}
390
392{
393 mChainFilters = chainFilters;
394}
395
396void QgsRelationReferenceWidget::setFilterExpression( const QString &expression )
397{
398 mFilterExpression = expression;
399}
400
402{
403 Q_UNUSED( e )
404
405 mShown = true;
406 if ( !mInitialized )
407 init();
408}
409
411{
412 if ( mReferencedLayer )
413 {
414 QApplication::setOverrideCursor( Qt::WaitCursor );
415
416 QSet<QString> requestedAttrs;
417
418 if ( !mFilterFields.isEmpty() )
419 {
420 for ( const QString &fieldName : std::as_const( mFilterFields ) )
421 {
422 int idx = mReferencedLayer->fields().lookupField( fieldName );
423
424 if ( idx == -1 )
425 continue;
426
427 QComboBox *cb = new QComboBox();
428 cb->setProperty( "Field", fieldName );
429 cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
430 mFilterComboBoxes << cb;
431 QVariantList uniqueValues = qgis::setToList( mReferencedLayer->uniqueValues( idx ) );
432 cb->addItem( mReferencedLayer->attributeDisplayName( idx ) );
433 QVariant nullValue = QgsApplication::nullRepresentation();
434 cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->fields().at( idx ).type() ) );
435
436 std::sort( uniqueValues.begin(), uniqueValues.end(), qgsVariantLessThan );
437 const auto constUniqueValues = uniqueValues;
438 for ( const QVariant &v : constUniqueValues )
439 {
440 cb->addItem( v.toString(), v );
441 }
442
443 connect( cb, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::filterChanged );
444
445 // Request this attribute for caching
446 requestedAttrs << fieldName;
447
448 mFilterLayout->addWidget( cb );
449 }
450
451 if ( mChainFilters )
452 {
453 QVariant nullValue = QgsApplication::nullRepresentation();
454
455 QgsFeature ft;
456 QgsFeatureIterator fit = mFilterExpression.isEmpty()
457 ? mReferencedLayer->getFeatures()
458 : mReferencedLayer->getFeatures( mFilterExpression );
459 while ( fit.nextFeature( ft ) )
460 {
461 const int count = std::min( mFilterComboBoxes.count(), mFilterFields.count() );
462 for ( int i = 0; i < count - 1; i++ )
463 {
464 QVariant cv = ft.attribute( mFilterFields.at( i ) );
465 QVariant nv = ft.attribute( mFilterFields.at( i + 1 ) );
466 QString cf = QgsVariantUtils::isNull( cv ) ? nullValue.toString() : cv.toString();
467 QString nf = QgsVariantUtils::isNull( nv ) ? nullValue.toString() : nv.toString();
468 mFilterCache[mFilterFields[i]][cf] << nf;
469 }
470 }
471
472 if ( !mFilterComboBoxes.isEmpty() )
473 {
474 QComboBox *cb = mFilterComboBoxes.first();
475 cb->setCurrentIndex( 0 );
476 disableChainedComboBoxes( cb );
477 }
478 }
479 }
480 else
481 {
482 mFilterContainer->hide();
483 }
484
485 mComboBox->setSourceLayer( mReferencedLayer );
486 mComboBox->setDisplayExpression( mReferencedLayer->displayExpression() );
487 mComboBox->setAllowNull( mAllowNull );
488 mComboBox->setIdentifierFields( mReferencedFields );
489
490 if ( ! mFilterExpression.isEmpty() )
491 mComboBox->setFilterExpression( mFilterExpression );
492
493 QVariant nullValue = QgsApplication::nullRepresentation();
494
495 if ( mChainFilters && mFeature.isValid() )
496 {
497 for ( int i = 0; i < mFilterFields.size(); i++ )
498 {
499 QVariant v = mFeature.attribute( mFilterFields[i] );
500 QString f = QgsVariantUtils::isNull( v ) ? nullValue.toString() : v.toString();
501 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
502 }
503 }
504
505 // Only connect after iterating, to have only one iterator on the referenced table at once
506 connect( mComboBox, &QgsFeatureListComboBox::currentFeatureChanged, this, &QgsRelationReferenceWidget::comboReferenceChanged );
507
508 QApplication::restoreOverrideCursor();
509
510 mInitialized = true;
511 }
512}
513
514void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
515{
516 if ( action == mHighlightFeatureAction )
517 {
518 highlightFeature();
519 }
520 else if ( action == mScaleHighlightFeatureAction )
521 {
522 highlightFeature( QgsFeature(), Scale );
523 }
524 else if ( action == mPanHighlightFeatureAction )
525 {
526 highlightFeature( QgsFeature(), Pan );
527 }
528}
529
531{
533
534 if ( !feat.isValid() )
535 return;
536
538 QgsAttributeDialog *attributeDialog = new QgsAttributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
539 attributeDialog->show();
540}
541
542void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
543{
544 if ( !mCanvas )
545 return;
546
547 if ( !f.isValid() )
548 {
549 f = referencedFeature();
550 if ( !f.isValid() )
551 return;
552 }
553
554 if ( !f.hasGeometry() )
555 {
556 return;
557 }
558
559 QgsGeometry geom = f.geometry();
560
561 // scale or pan
562 if ( canvasExtent == Scale )
563 {
564 QgsRectangle featBBox = geom.boundingBox();
565 featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
566 QgsRectangle extent = mCanvas->extent();
567 if ( !extent.contains( featBBox ) )
568 {
569 extent.combineExtentWith( featBBox );
570 extent.scale( 1.1 );
571 mCanvas->setExtent( extent, true );
572 mCanvas->refresh();
573 }
574 }
575 else if ( canvasExtent == Pan )
576 {
578 QgsPointXY center = centroid.asPoint();
579 center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
580 mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
581 }
582
583 // highlight
584 deleteHighlight();
585 mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
586 mHighlight->applyDefaultStyle();
587 mHighlight->show();
588
589 QTimer *timer = new QTimer( this );
590 timer->setSingleShot( true );
591 connect( timer, &QTimer::timeout, this, &QgsRelationReferenceWidget::deleteHighlight );
592 timer->start( 3000 );
593}
594
595void QgsRelationReferenceWidget::deleteHighlight()
596{
597 if ( mHighlight )
598 {
599 mHighlight->hide();
600 delete mHighlight;
601 }
602 mHighlight = nullptr;
603}
604
606{
607 if ( !mAllowMapIdentification || !mReferencedLayer )
608 return;
609
610 const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
611 if ( !tools )
612 return;
613 if ( !mCanvas )
614 return;
615
616 mMapToolIdentify->setLayer( mReferencedLayer );
617 setMapTool( mMapToolIdentify );
618
619 connect( mMapToolIdentify, qOverload<const QgsFeature &>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
620
621 if ( mMessageBar )
622 {
623 QString title = tr( "Relation %1 for %2." ).arg( mRelation.name(), mReferencingLayer->name() );
624 QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
625 mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
626 mMessageBar->pushItem( mMessageBarItem );
627 }
628}
629
630void QgsRelationReferenceWidget::comboReferenceChanged()
631{
632 mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( mFeature );
633 highlightFeature( mFeature );
634 updateAttributeEditorFrame( mFeature );
635
636 emitForeignKeysChanged( mComboBox->identifierValues() );
637}
638
639void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature &feature )
640{
641 mOpenFormButton->setEnabled( feature.isValid() );
642 // Check if we're running with an embedded frame we need to update
643 if ( mAttributeEditorFrame && mReferencedAttributeForm )
644 {
645 mReferencedAttributeForm->setFeature( feature );
646 }
647}
648
650{
651 return mAllowAddFeatures;
652}
653
655{
656 mAllowAddFeatures = allowAddFeatures;
657 updateAddEntryButton();
658}
659
661{
662 return mRelation;
663}
664
665void QgsRelationReferenceWidget::featureIdentified( const QgsFeature &feature )
666{
667 mComboBox->setCurrentFeature( feature );
668 mFeature = feature;
669
670 mRemoveFKButton->setEnabled( mIsEditable );
671 highlightFeature( feature );
672 updateAttributeEditorFrame( feature );
673 emitForeignKeysChanged( foreignKeys(), true );
674
675 unsetMapTool();
676}
677
678void QgsRelationReferenceWidget::setMapTool( QgsMapTool *mapTool )
679{
680 mCurrentMapTool = mapTool;
681 mCanvas->setMapTool( mapTool );
682
683 mWindowWidget = window();
684
685 mCanvas->window()->raise();
686 mCanvas->activateWindow();
687 mCanvas->setFocus();
688 connect( mapTool, &QgsMapTool::deactivated, this, &QgsRelationReferenceWidget::mapToolDeactivated );
689}
690
691void QgsRelationReferenceWidget::unsetMapTool()
692{
693 // deactivate map tools if activated
694 if ( mCurrentMapTool )
695 {
696 /* this will call mapToolDeactivated */
697 mCanvas->unsetMapTool( mCurrentMapTool );
698
699 if ( mCurrentMapTool == mMapToolDigitize )
700 {
701 disconnect( mCanvas, &QgsMapCanvas::keyPressed, this, &QgsRelationReferenceWidget::onKeyPressed );
702 disconnect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationReferenceWidget::entryAdded );
703 }
704 else
705 {
706 disconnect( mMapToolIdentify, qOverload<const QgsFeature &>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
707 }
708 }
709}
710
711void QgsRelationReferenceWidget::onKeyPressed( QKeyEvent *e )
712{
713 if ( e->key() == Qt::Key_Escape )
714 {
715 unsetMapTool();
716 }
717}
718
719void QgsRelationReferenceWidget::mapToolDeactivated()
720{
721 if ( mWindowWidget )
722 {
723 mWindowWidget->raise();
724 mWindowWidget->activateWindow();
725 }
726
727 if ( mMessageBar && mMessageBarItem )
728 {
729 mMessageBar->popWidget( mMessageBarItem );
730 }
731 mMessageBarItem = nullptr;
732}
733
734void QgsRelationReferenceWidget::filterChanged()
735{
736 QVariant nullValue = QgsApplication::nullRepresentation();
737
738 QMap<QString, QString> filters;
739 QgsAttributeList attrs;
740
741 QComboBox *scb = qobject_cast<QComboBox *>( sender() );
742
743 Q_ASSERT( scb );
744
745 QgsFeature f;
746 QgsFeatureIds featureIds;
747 QString filterExpression = mFilterExpression;
748
749 // wrap the expression with parentheses as it might contain `OR`
750 if ( !filterExpression.isEmpty() )
751 filterExpression = QStringLiteral( " ( %1 ) " ).arg( filterExpression );
752
753 // comboboxes have to be disabled before building filters
754 if ( mChainFilters )
755 disableChainedComboBoxes( scb );
756
757 // build filters
758 const auto constMFilterComboBoxes = mFilterComboBoxes;
759 for ( QComboBox *cb : constMFilterComboBoxes )
760 {
761 if ( cb->currentIndex() != 0 )
762 {
763 const QString fieldName = cb->property( "Field" ).toString();
764
765 if ( cb->currentText() == nullValue.toString() )
766 {
767 filters[fieldName] = QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
768 }
769 else
770 {
771 filters[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
772 }
773 attrs << mReferencedLayer->fields().lookupField( fieldName );
774 }
775 }
776
777 if ( mChainFilters )
778 {
779 QComboBox *ccb = nullptr;
780 const auto constMFilterComboBoxes = mFilterComboBoxes;
781 for ( QComboBox *cb : constMFilterComboBoxes )
782 {
783 if ( !ccb )
784 {
785 if ( cb == scb )
786 ccb = cb;
787
788 continue;
789 }
790
791 if ( ccb->currentIndex() != 0 )
792 {
793 const QString fieldName = cb->property( "Field" ).toString();
794
795 cb->blockSignals( true );
796 cb->clear();
797 cb->addItem( cb->property( "FieldAlias" ).toString() );
798
799 // ccb = scb
800 // cb = scb + 1
801 QStringList texts;
802 const auto txts { mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] };
803 for ( const QString &txt : txts )
804 {
805 QMap<QString, QString> filtersAttrs = filters;
806 filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt );
807 QgsAttributeList subset = attrs;
808
809 QString expression = filterExpression;
810 if ( ! filterExpression.isEmpty() && ! filtersAttrs.isEmpty() )
811 expression += QLatin1String( " AND " );
812
813 expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral( " ( " );
814 expression += qgsMapJoinValues( filtersAttrs, QLatin1String( " AND " ) );
815 expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral( " ) " );
816
817 subset << mReferencedLayer->fields().lookupField( fieldName );
818
819 QgsFeatureIterator it( mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
820
821 bool found = false;
822 while ( it.nextFeature( f ) )
823 {
824 if ( !featureIds.contains( f.id() ) )
825 featureIds << f.id();
826
827 found = true;
828 }
829
830 // item is only provided if at least 1 feature exists
831 if ( found )
832 texts << txt;
833 }
834
835 texts.sort();
836 cb->addItems( texts );
837
838 cb->setEnabled( true );
839 cb->blockSignals( false );
840
841 ccb = cb;
842 }
843 }
844 }
845
846 if ( ! filterExpression.isEmpty() && ! filters.isEmpty() )
847 filterExpression += QLatin1String( " AND " );
848
849 filterExpression += filters.isEmpty() ? QString() : QStringLiteral( " ( " );
850 filterExpression += qgsMapJoinValues( filters, QLatin1String( " AND " ) );
851 filterExpression += filters.isEmpty() ? QString() : QStringLiteral( " ) " );
852
854}
855
856void QgsRelationReferenceWidget::addEntry()
857{
858 if ( !mReferencedLayer )
859 return;
860
861 const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
862 if ( !tools )
863 return;
864 if ( !mCanvas )
865 return;
866
867 // no geometry, skip the digitizing
868 if ( mReferencedLayer->geometryType() == QgsWkbTypes::UnknownGeometry || mReferencedLayer->geometryType() == QgsWkbTypes::NullGeometry )
869 {
870 QgsFeature f( mReferencedLayer->fields() );
871 entryAdded( f );
872 return;
873 }
874
875 mMapToolDigitize->setLayer( mReferencedLayer );
876 setMapTool( mMapToolDigitize );
877
878 connect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationReferenceWidget::entryAdded );
879 connect( mCanvas, &QgsMapCanvas::keyPressed, this, &QgsRelationReferenceWidget::onKeyPressed );
880
881 if ( mMessageBar )
882 {
883 QString title = tr( "Relation %1 for %2." ).arg( mRelation.name(), mReferencingLayer->name() );
884
885 QString displayString = QgsVectorLayerUtils::getFeatureDisplayString( mReferencingLayer, mFormFeature );
886 QString msg = tr( "Link feature to %1 \"%2\" : Digitize the geometry for the new feature on layer %3. Press &lt;ESC&gt; to cancel." )
887 .arg( mReferencingLayer->name(), displayString, mReferencedLayer->name() );
888 mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
889 mMessageBar->pushItem( mMessageBarItem );
890 }
891
892}
893
894void QgsRelationReferenceWidget::entryAdded( const QgsFeature &feat )
895{
896 QgsFeature f( feat );
897 QgsAttributeMap attributes;
898
899 // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
900 if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
901 {
902 int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
903
904 if ( fieldIdx != -1 )
905 {
906 attributes.insert( fieldIdx, mComboBox->currentText() );
907 }
908 }
909
910 if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, f.geometry(), &f, this, false, true ) )
911 {
912 QVariantList attrs;
913 for ( const QString &fieldName : std::as_const( mReferencedFields ) )
914 attrs << f.attribute( fieldName );
915
916 setForeignKeys( attrs );
917
918 mAddEntryButton->setEnabled( false );
919 }
920
921 unsetMapTool();
922}
923
924void QgsRelationReferenceWidget::updateAddEntryButton()
925{
926 mAddEntryButton->setVisible( mAllowAddFeatures && mMapToolDigitize );
927 mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
928}
929
930void QgsRelationReferenceWidget::disableChainedComboBoxes( const QComboBox *scb )
931{
932 QComboBox *ccb = nullptr;
933 const auto constMFilterComboBoxes = mFilterComboBoxes;
934 for ( QComboBox *cb : constMFilterComboBoxes )
935 {
936 if ( !ccb )
937 {
938 if ( cb == scb )
939 {
940 ccb = cb;
941 }
942
943 continue;
944 }
945
946 cb->setCurrentIndex( 0 );
947 if ( ccb->currentIndex() == 0 )
948 {
949 cb->setEnabled( false );
950 }
951
952 ccb = cb;
953 }
954}
955
956void QgsRelationReferenceWidget::emitForeignKeysChanged( const QVariantList &foreignKeys, bool force )
957{
958 if ( foreignKeys == mForeignKeys && force == false && qVariantListIsNull( foreignKeys ) == qVariantListIsNull( mForeignKeys ) )
959 return;
960
961 mForeignKeys = foreignKeys;
963 emit foreignKeyChanged( foreignKeys.at( 0 ) );
966}
967
969{
970 return mReferencedLayerName;
971}
972
973void QgsRelationReferenceWidget::setReferencedLayerName( const QString &relationLayerName )
974{
975 mReferencedLayerName = relationLayerName;
976}
977
979{
980 return mReferencedLayerId;
981}
982
983void QgsRelationReferenceWidget::setReferencedLayerId( const QString &relationLayerId )
984{
985 mReferencedLayerId = relationLayerId;
986}
987
989{
990 return mReferencedLayerProviderKey;
991}
992
993void QgsRelationReferenceWidget::setReferencedLayerProviderKey( const QString &relationProviderKey )
994{
995 mReferencedLayerProviderKey = relationProviderKey;
996}
997
999{
1000 return mReferencedLayerDataSource;
1001}
1002
1003void QgsRelationReferenceWidget::setReferencedLayerDataSource( const QString &relationDataSource )
1004{
1005 const QgsPathResolver resolver { QgsProject::instance()->pathResolver() };
1006 mReferencedLayerDataSource = resolver.writePath( relationDataSource );
1007}
1008
1010{
1011 mFormFeature = formFeature;
1012}
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:58
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:349
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:476
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.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
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
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:3366
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:2869
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:3365
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:2708
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)