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