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