QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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 mComboBox->setFetchLimit( mFetchLimit );
191 }
192 mAttributeEditorFrame->setObjectName( QStringLiteral( "referencing/" ) + relation.name() );
193
194 if ( mEmbedForm )
195 {
197 mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
198 mReferencedAttributeForm = new QgsAttributeForm( relation.referencedLayer(), QgsFeature(), context, this );
199 mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
200 }
201
202 connect( mReferencedLayer, &QgsVectorLayer::editingStarted, this, &QgsRelationReferenceWidget::updateAddEntryButton );
203 connect( mReferencedLayer, &QgsVectorLayer::editingStopped, this, &QgsRelationReferenceWidget::updateAddEntryButton );
204 updateAddEntryButton();
205 }
206 else
207 {
208 mInvalidLabel->show();
209 }
210
211 if ( mShown && isVisible() )
212 {
213 init();
214 }
215}
216
218{
219 if ( !editable )
220 {
221 unsetMapTool();
222 }
223
224 mFilterContainer->setEnabled( editable );
225 mComboBox->setEnabled( editable && !mReadOnlySelector );
226 mComboBox->setEditable( true );
227 mMapIdentificationButton->setEnabled( editable );
228 mRemoveFKButton->setEnabled( editable );
229 mIsEditable = editable;
230}
231
232void QgsRelationReferenceWidget::setForeignKey( const QVariant &value )
233{
234 setForeignKeys( QVariantList() << value );
235}
236
237void QgsRelationReferenceWidget::setForeignKeys( const QVariantList &values )
238{
239 if ( values.isEmpty() )
240 {
241 return;
242 }
243 if ( qVariantListIsNull( values ) )
244 {
246 return;
247 }
248
249 if ( !mReferencedLayer )
250 return;
251
252 mComboBox->setIdentifierValues( values );
253
254 if ( mEmbedForm || mChainFilters )
255 {
256 QgsFeatureRequest request = mComboBox->currentFeatureRequest();
257 mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
258 }
259 if ( mChainFilters )
260 {
261 QVariant nullValue = QgsApplication::nullRepresentation();
262 const int count = std::min( mFilterComboBoxes.size(), mFilterFields.size() );
263 for ( int i = 0; i < count; i++ )
264 {
265 QVariant v = mFeature.attribute( mFilterFields[i] );
266 QString f = QgsVariantUtils::isNull( v ) ? nullValue.toString() : v.toString();
267 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
268 }
269 }
270
271 mRemoveFKButton->setEnabled( mIsEditable );
272 highlightFeature( mFeature ); // TODO : make this async
273 updateAttributeEditorFrame( mFeature );
274
275 emitForeignKeysChanged( foreignKeys() );
276}
277
279{
280 // deactivate filter comboboxes
281 if ( mChainFilters && !mFilterComboBoxes.isEmpty() )
282 {
283 QComboBox *cb = mFilterComboBoxes.first();
284 cb->setCurrentIndex( 0 );
285 disableChainedComboBoxes( cb );
286 }
287
288 mComboBox->setIdentifierValuesToNull();
289 mRemoveFKButton->setEnabled( false );
290 updateAttributeEditorFrame( QgsFeature() );
291
292 emitForeignKeysChanged( foreignKeys() );
293}
294
296{
297 QgsFeature f;
298 if ( mReferencedLayer )
299 {
300 mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( f );
301 }
302 return f;
303}
304
306{
307 whileBlocking( mComboBox )->setIdentifierValuesToNull();
308 mRemoveFKButton->setEnabled( false );
309 updateAttributeEditorFrame( QgsFeature() );
310}
311
313{
314 QVariantList fkeys;
315 if ( fkeys.isEmpty() )
316 return QVariant( QVariant::Int );
317 else
318 return fkeys.at( 0 );
319}
320
322{
323 return mComboBox->identifierValues();
324}
325
327{
328 mEditorContext = context;
329 mCanvas = canvas;
330 mMessageBar = messageBar;
331
332 mMapToolIdentify.reset( new QgsMapToolIdentifyFeature( mCanvas ) );
333 mMapToolIdentify->setButton( mMapIdentificationButton );
334
335 if ( mEditorContext.cadDockWidget() )
336 {
337 mMapToolDigitize.reset( new QgsMapToolDigitizeFeature( mCanvas, mEditorContext.cadDockWidget() ) );
338 mMapToolDigitize->setButton( mAddEntryButton );
339 updateAddEntryButton();
340 }
341}
342
344{
345 if ( display )
346 {
347 setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
348 mTopLayout->setAlignment( Qt::AlignTop );
349 }
350
351 mAttributeEditorFrame->setVisible( display );
352 mEmbedForm = display;
353}
354
356{
357 mComboBox->setEnabled( !readOnly );
358 mRemoveFKButton->setVisible( mAllowNull && readOnly );
359 mReadOnlySelector = readOnly;
360}
361
363{
364 mHighlightFeatureButton->setVisible( allowMapIdentification );
365 mMapIdentificationButton->setVisible( allowMapIdentification );
366 mAllowMapIdentification = allowMapIdentification;
367}
368
369void QgsRelationReferenceWidget::setFilterFields( const QStringList &filterFields )
370{
371 mFilterFields = filterFields;
372}
373
375{
376 mOpenFormButton->setVisible( openFormButtonVisible );
377 mOpenFormButtonVisible = openFormButtonVisible;
378}
379
381{
382 mChainFilters = chainFilters;
383}
384
385void QgsRelationReferenceWidget::setFilterExpression( const QString &expression )
386{
387 mFilterExpression = expression;
388}
389
391{
392 Q_UNUSED( e )
393
394 mShown = true;
395 if ( !mInitialized )
396 init();
397}
398
400{
401 if ( mReferencedLayer )
402 {
403 QApplication::setOverrideCursor( Qt::WaitCursor );
404
405 QSet<QString> requestedAttrs;
406
407 if ( !mFilterFields.isEmpty() )
408 {
409 for ( const QString &fieldName : std::as_const( mFilterFields ) )
410 {
411 int idx = mReferencedLayer->fields().lookupField( fieldName );
412
413 if ( idx == -1 )
414 continue;
415
416 QComboBox *cb = new QComboBox();
417 cb->setProperty( "Field", fieldName );
418 cb->setProperty( "FieldAlias", mReferencedLayer->attributeDisplayName( idx ) );
419 mFilterComboBoxes << cb;
420 QVariantList uniqueValues = qgis::setToList( mReferencedLayer->uniqueValues( idx ) );
421 cb->addItem( mReferencedLayer->attributeDisplayName( idx ) );
422 QVariant nullValue = QgsApplication::nullRepresentation();
423 cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->fields().at( idx ).type() ) );
424
425 std::sort( uniqueValues.begin(), uniqueValues.end(), qgsVariantLessThan );
426 const auto constUniqueValues = uniqueValues;
427 for ( const QVariant &v : constUniqueValues )
428 {
429 cb->addItem( v.toString(), v );
430 }
431
432 connect( cb, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsRelationReferenceWidget::filterChanged );
433
434 // Request this attribute for caching
435 requestedAttrs << fieldName;
436
437 mFilterLayout->addWidget( cb );
438 }
439
440 if ( mChainFilters )
441 {
442 QVariant nullValue = QgsApplication::nullRepresentation();
443
444 QgsFeature ft;
445 QgsFeatureIterator fit = mFilterExpression.isEmpty()
446 ? mReferencedLayer->getFeatures()
447 : mReferencedLayer->getFeatures( mFilterExpression );
448 while ( fit.nextFeature( ft ) )
449 {
450 const int count = std::min( mFilterComboBoxes.count(), mFilterFields.count() );
451 for ( int i = 0; i < count - 1; i++ )
452 {
453 QVariant cv = ft.attribute( mFilterFields.at( i ) );
454 QVariant nv = ft.attribute( mFilterFields.at( i + 1 ) );
455 QString cf = QgsVariantUtils::isNull( cv ) ? nullValue.toString() : cv.toString();
456 QString nf = QgsVariantUtils::isNull( nv ) ? nullValue.toString() : nv.toString();
457 mFilterCache[mFilterFields[i]][cf] << nf;
458 }
459 }
460
461 if ( !mFilterComboBoxes.isEmpty() )
462 {
463 QComboBox *cb = mFilterComboBoxes.first();
464 cb->setCurrentIndex( 0 );
465 disableChainedComboBoxes( cb );
466 }
467 }
468 }
469 else
470 {
471 mFilterContainer->hide();
472 }
473
474 mComboBox->setSourceLayer( mReferencedLayer );
475 mComboBox->setDisplayExpression( mReferencedLayer->displayExpression() );
476 mComboBox->setAllowNull( mAllowNull );
477 mComboBox->setIdentifierFields( mReferencedFields );
478 mComboBox->setFetchLimit( mFetchLimit );
479
480 if ( ! mFilterExpression.isEmpty() )
481 mComboBox->setFilterExpression( mFilterExpression );
482
483 QVariant nullValue = QgsApplication::nullRepresentation();
484
485 if ( mChainFilters && mFeature.isValid() )
486 {
487 for ( int i = 0; i < mFilterFields.size(); i++ )
488 {
489 QVariant v = mFeature.attribute( mFilterFields[i] );
490 QString f = QgsVariantUtils::isNull( v ) ? nullValue.toString() : v.toString();
491 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
492 }
493 }
494
495 // Only connect after iterating, to have only one iterator on the referenced table at once
496 connect( mComboBox, &QgsFeatureListComboBox::currentFeatureChanged, this, &QgsRelationReferenceWidget::comboReferenceChanged );
497
498 QApplication::restoreOverrideCursor();
499
500 mInitialized = true;
501 }
502}
503
504void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
505{
506 if ( action == mHighlightFeatureAction )
507 {
508 highlightFeature();
509 }
510 else if ( action == mScaleHighlightFeatureAction )
511 {
512 highlightFeature( QgsFeature(), Scale );
513 }
514 else if ( action == mPanHighlightFeatureAction )
515 {
516 highlightFeature( QgsFeature(), Pan );
517 }
518}
519
521{
523
524 if ( !feat.isValid() )
525 return;
526
528 QgsAttributeDialog *attributeDialog = new QgsAttributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
529 attributeDialog->show();
530}
531
532void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
533{
534 if ( !mCanvas )
535 return;
536
537 if ( !f.isValid() )
538 {
539 f = referencedFeature();
540 if ( !f.isValid() )
541 return;
542 }
543
544 if ( !f.hasGeometry() )
545 {
546 return;
547 }
548
549 QgsGeometry geom = f.geometry();
550
551 // scale or pan
552 if ( canvasExtent == Scale )
553 {
554 QgsRectangle featBBox = geom.boundingBox();
555 featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
556 QgsRectangle extent = mCanvas->extent();
557 if ( !extent.contains( featBBox ) )
558 {
559 extent.combineExtentWith( featBBox );
560 extent.scale( 1.1 );
561 mCanvas->setExtent( extent, true );
562 mCanvas->refresh();
563 }
564 }
565 else if ( canvasExtent == Pan )
566 {
568 QgsPointXY center = centroid.asPoint();
569 center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
570 mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
571 }
572
573 // highlight
574 deleteHighlight();
575 mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
576 mHighlight->applyDefaultStyle();
577 mHighlight->show();
578
579 QTimer *timer = new QTimer( this );
580 timer->setSingleShot( true );
581 connect( timer, &QTimer::timeout, this, &QgsRelationReferenceWidget::deleteHighlight );
582 timer->start( 3000 );
583}
584
585void QgsRelationReferenceWidget::deleteHighlight()
586{
587 if ( mHighlight )
588 {
589 mHighlight->hide();
590 delete mHighlight;
591 }
592 mHighlight = nullptr;
593}
594
596{
597 if ( !mAllowMapIdentification || !mReferencedLayer )
598 return;
599
600 const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
601 if ( !tools )
602 return;
603 if ( !mCanvas )
604 return;
605
606 mMapToolIdentify->setLayer( mReferencedLayer );
607 setMapTool( mMapToolIdentify );
608
609 connect( mMapToolIdentify, qOverload<const QgsFeature &>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
610
611 if ( mMessageBar )
612 {
613 QString title = tr( "Relation %1 for %2." ).arg( mRelation.name(), mReferencingLayer->name() );
614 QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
615 mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
616 mMessageBar->pushItem( mMessageBarItem );
617 }
618}
619
620void QgsRelationReferenceWidget::comboReferenceChanged()
621{
622 mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( mFeature );
623 highlightFeature( mFeature );
624 updateAttributeEditorFrame( mFeature );
625
626 emitForeignKeysChanged( mComboBox->identifierValues() );
627}
628
629void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature &feature )
630{
631 mOpenFormButton->setEnabled( feature.isValid() );
632 // Check if we're running with an embedded frame we need to update
633 if ( mAttributeEditorFrame && mReferencedAttributeForm )
634 {
635 mReferencedAttributeForm->setFeature( feature );
636 }
637}
638
640{
641 return mAllowAddFeatures;
642}
643
645{
646 mAllowAddFeatures = allowAddFeatures;
647 updateAddEntryButton();
648}
649
651{
652 return mRelation;
653}
654
655void QgsRelationReferenceWidget::featureIdentified( const QgsFeature &feature )
656{
657 mComboBox->setCurrentFeature( feature );
658 mFeature = feature;
659
660 mRemoveFKButton->setEnabled( mIsEditable );
661 highlightFeature( feature );
662 updateAttributeEditorFrame( feature );
663 emitForeignKeysChanged( foreignKeys(), true );
664
665 unsetMapTool();
666}
667
668void QgsRelationReferenceWidget::setMapTool( QgsMapTool *mapTool )
669{
670 mCurrentMapTool = mapTool;
671 mCanvas->setMapTool( mapTool );
672
673 mWindowWidget = window();
674
675 mCanvas->window()->raise();
676 mCanvas->activateWindow();
677 mCanvas->setFocus();
678 connect( mapTool, &QgsMapTool::deactivated, this, &QgsRelationReferenceWidget::mapToolDeactivated );
679}
680
681void QgsRelationReferenceWidget::unsetMapTool()
682{
683 // deactivate map tools if activated
684 if ( mCurrentMapTool )
685 {
686 /* this will call mapToolDeactivated */
687 mCanvas->unsetMapTool( mCurrentMapTool );
688
689 if ( mCurrentMapTool == mMapToolDigitize )
690 {
691 disconnect( mCanvas, &QgsMapCanvas::keyPressed, this, &QgsRelationReferenceWidget::onKeyPressed );
692 disconnect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationReferenceWidget::entryAdded );
693 }
694 else
695 {
696 disconnect( mMapToolIdentify, qOverload<const QgsFeature &>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
697 }
698 }
699}
700
701void QgsRelationReferenceWidget::onKeyPressed( QKeyEvent *e )
702{
703 if ( e->key() == Qt::Key_Escape )
704 {
705 unsetMapTool();
706 }
707}
708
709void QgsRelationReferenceWidget::mapToolDeactivated()
710{
711 if ( mWindowWidget )
712 {
713 mWindowWidget->raise();
714 mWindowWidget->activateWindow();
715 }
716
717 if ( mMessageBar && mMessageBarItem )
718 {
719 mMessageBar->popWidget( mMessageBarItem );
720 }
721 mMessageBarItem = nullptr;
722}
723
724void QgsRelationReferenceWidget::filterChanged()
725{
726 QVariant nullValue = QgsApplication::nullRepresentation();
727
728 QMap<QString, QString> filters;
729 QgsAttributeList attrs;
730
731 QComboBox *scb = qobject_cast<QComboBox *>( sender() );
732
733 Q_ASSERT( scb );
734
735 QgsFeature f;
736 QgsFeatureIds featureIds;
737 QString filterExpression = mFilterExpression;
738
739 // wrap the expression with parentheses as it might contain `OR`
740 if ( !filterExpression.isEmpty() )
741 filterExpression = QStringLiteral( " ( %1 ) " ).arg( filterExpression );
742
743 // comboboxes have to be disabled before building filters
744 if ( mChainFilters )
745 disableChainedComboBoxes( scb );
746
747 // build filters
748 const auto constMFilterComboBoxes = mFilterComboBoxes;
749 for ( QComboBox *cb : constMFilterComboBoxes )
750 {
751 if ( cb->currentIndex() != 0 )
752 {
753 const QString fieldName = cb->property( "Field" ).toString();
754
755 if ( cb->currentText() == nullValue.toString() )
756 {
757 filters[fieldName] = QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
758 }
759 else
760 {
761 filters[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
762 }
763 attrs << mReferencedLayer->fields().lookupField( fieldName );
764 }
765 }
766
767 if ( mChainFilters )
768 {
769 QComboBox *ccb = nullptr;
770 const auto constMFilterComboBoxes = mFilterComboBoxes;
771 for ( QComboBox *cb : constMFilterComboBoxes )
772 {
773 if ( !ccb )
774 {
775 if ( cb == scb )
776 ccb = cb;
777
778 continue;
779 }
780
781 if ( ccb->currentIndex() != 0 )
782 {
783 const QString fieldName = cb->property( "Field" ).toString();
784
785 cb->blockSignals( true );
786 cb->clear();
787 cb->addItem( cb->property( "FieldAlias" ).toString() );
788
789 // ccb = scb
790 // cb = scb + 1
791 QStringList texts;
792 const auto txts { mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] };
793 for ( const QString &txt : txts )
794 {
795 QMap<QString, QString> filtersAttrs = filters;
796 filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt );
797 QgsAttributeList subset = attrs;
798
799 QString expression = filterExpression;
800 if ( ! filterExpression.isEmpty() && ! filtersAttrs.isEmpty() )
801 expression += QLatin1String( " AND " );
802
803 expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral( " ( " );
804 expression += qgsMapJoinValues( filtersAttrs, QLatin1String( " AND " ) );
805 expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral( " ) " );
806
807 subset << mReferencedLayer->fields().lookupField( fieldName );
808
809 QgsFeatureIterator it( mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
810
811 bool found = false;
812 while ( it.nextFeature( f ) )
813 {
814 if ( !featureIds.contains( f.id() ) )
815 featureIds << f.id();
816
817 found = true;
818 }
819
820 // item is only provided if at least 1 feature exists
821 if ( found )
822 texts << txt;
823 }
824
825 texts.sort();
826 cb->addItems( texts );
827
828 cb->setEnabled( true );
829 cb->blockSignals( false );
830
831 ccb = cb;
832 }
833 }
834 }
835
836 if ( ! filterExpression.isEmpty() && ! filters.isEmpty() )
837 filterExpression += QLatin1String( " AND " );
838
839 filterExpression += filters.isEmpty() ? QString() : QStringLiteral( " ( " );
840 filterExpression += qgsMapJoinValues( filters, QLatin1String( " AND " ) );
841 filterExpression += filters.isEmpty() ? QString() : QStringLiteral( " ) " );
842
844}
845
846void QgsRelationReferenceWidget::addEntry()
847{
848 if ( !mReferencedLayer )
849 return;
850
851 const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
852 if ( !tools )
853 return;
854 if ( !mCanvas )
855 return;
856
857 // no geometry, skip the digitizing
858 if ( mReferencedLayer->geometryType() == Qgis::GeometryType::Unknown || mReferencedLayer->geometryType() == Qgis::GeometryType::Null )
859 {
860 QgsFeature f( mReferencedLayer->fields() );
861 entryAdded( f );
862 return;
863 }
864
865 mMapToolDigitize->setLayer( mReferencedLayer );
866 setMapTool( mMapToolDigitize );
867
868 connect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationReferenceWidget::entryAdded );
869 connect( mCanvas, &QgsMapCanvas::keyPressed, this, &QgsRelationReferenceWidget::onKeyPressed );
870
871 if ( mMessageBar )
872 {
873 QString title = tr( "Relation %1 for %2." ).arg( mRelation.name(), mReferencingLayer->name() );
874
875 QString displayString = QgsVectorLayerUtils::getFeatureDisplayString( mReferencingLayer, mFormFeature );
876 QString msg = tr( "Link feature to %1 \"%2\" : Digitize the geometry for the new feature on layer %3. Press &lt;ESC&gt; to cancel." )
877 .arg( mReferencingLayer->name(), displayString, mReferencedLayer->name() );
878 mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
879 mMessageBar->pushItem( mMessageBarItem );
880 }
881
882}
883
884void QgsRelationReferenceWidget::entryAdded( const QgsFeature &feat )
885{
886 QgsFeature f( feat );
887 QgsAttributeMap attributes;
888
889 // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
890 if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
891 {
892 int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
893
894 if ( fieldIdx != -1 )
895 {
896 attributes.insert( fieldIdx, mComboBox->currentText() );
897 }
898 }
899
900 if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, f.geometry(), &f, this, false, true ) )
901 {
902 QVariantList attrs;
903 for ( const QString &fieldName : std::as_const( mReferencedFields ) )
904 attrs << f.attribute( fieldName );
905
906 setForeignKeys( attrs );
907
908 mAddEntryButton->setEnabled( false );
909 }
910
911 unsetMapTool();
912}
913
914void QgsRelationReferenceWidget::updateAddEntryButton()
915{
916 mAddEntryButton->setVisible( mAllowAddFeatures && mMapToolDigitize );
917 mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
918}
919
920void QgsRelationReferenceWidget::disableChainedComboBoxes( const QComboBox *scb )
921{
922 QComboBox *ccb = nullptr;
923 const auto constMFilterComboBoxes = mFilterComboBoxes;
924 for ( QComboBox *cb : constMFilterComboBoxes )
925 {
926 if ( !ccb )
927 {
928 if ( cb == scb )
929 {
930 ccb = cb;
931 }
932
933 continue;
934 }
935
936 cb->setCurrentIndex( 0 );
937 if ( ccb->currentIndex() == 0 )
938 {
939 cb->setEnabled( false );
940 }
941
942 ccb = cb;
943 }
944}
945
946void QgsRelationReferenceWidget::emitForeignKeysChanged( const QVariantList &foreignKeys, bool force )
947{
948 if ( foreignKeys == mForeignKeys && force == false && qVariantListIsNull( foreignKeys ) == qVariantListIsNull( mForeignKeys ) )
949 return;
950
951 mForeignKeys = foreignKeys;
953 emit foreignKeyChanged( foreignKeys.at( 0 ) );
956}
957
959{
960 return mReferencedLayerName;
961}
962
963void QgsRelationReferenceWidget::setReferencedLayerName( const QString &relationLayerName )
964{
965 mReferencedLayerName = relationLayerName;
966}
967
969{
970 return mReferencedLayerId;
971}
972
973void QgsRelationReferenceWidget::setReferencedLayerId( const QString &relationLayerId )
974{
975 mReferencedLayerId = relationLayerId;
976}
977
979{
980 return mReferencedLayerProviderKey;
981}
982
983void QgsRelationReferenceWidget::setReferencedLayerProviderKey( const QString &relationProviderKey )
984{
985 mReferencedLayerProviderKey = relationProviderKey;
986}
987
989{
990 return mReferencedLayerDataSource;
991}
992
993void QgsRelationReferenceWidget::setReferencedLayerDataSource( const QString &relationDataSource )
994{
995 const QgsPathResolver resolver { QgsProject::instance()->pathResolver() };
996 mReferencedLayerDataSource = resolver.writePath( relationDataSource );
997}
998
1000{
1001 mFormFeature = formFeature;
1002}
void reset(T *p=nullptr)
Will reset the managed pointer to p.
@ Unknown
Unknown types.
@ Null
No geometry.
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)
Fetch next feature and stores in f, returns true on success.
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.
void setFetchLimit(int fetchLimit)
Defines the feature request fetch limit If set to 0, no limit is applied when fetching.
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:230
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:216
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:335
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
QVariant::Type type
Definition: qgsfield.h:60
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:162
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:93
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:78
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.
void editingStarted()
Emitted when editing on this layer has started.
QString publicSource(bool hidePassword=false) const
Gets a version of the internal layer definition that has sensitive bits removed (for example,...
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:156
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:60
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:481
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:267
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:385
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:413
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...
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 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, bool silenceNullWarnings=false)
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:120
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:5776
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:5281
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:5775
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:5111
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:42
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
QList< int > QgsAttributeList
Definition: qgsfield.h:27
bool qVariantListIsNull(const QVariantList &list)