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