QGIS API Documentation 3.39.0-Master (47f7b3a4989)
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 <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 QgsVariantUtils::createNullVariant( QMetaType::Type::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(), QgsVariantUtils::createNullVariant( 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 // To avoid wrongly signaling a foreign key change, handle model feature found state following feature gathering separately
498 connect( mComboBox, &QgsFeatureListComboBox::currentFeatureFoundChanged, this, &QgsRelationReferenceWidget::comboReferenceFoundChanged );
499
500 QApplication::restoreOverrideCursor();
501
502 mInitialized = true;
503 }
504}
505
506void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
507{
508 if ( action == mHighlightFeatureAction )
509 {
510 highlightFeature();
511 }
512 else if ( action == mScaleHighlightFeatureAction )
513 {
514 highlightFeature( QgsFeature(), Scale );
515 }
516 else if ( action == mPanHighlightFeatureAction )
517 {
518 highlightFeature( QgsFeature(), Pan );
519 }
520}
521
523{
525
526 if ( !feat.isValid() )
527 return;
528
530 QgsAttributeDialog *attributeDialog = new QgsAttributeDialog( mReferencedLayer, new QgsFeature( feat ), true, this, true, context );
531 attributeDialog->show();
532}
533
534void QgsRelationReferenceWidget::highlightFeature( QgsFeature f, CanvasExtent canvasExtent )
535{
536 if ( !mCanvas )
537 return;
538
539 if ( !f.isValid() )
540 {
541 f = referencedFeature();
542 if ( !f.isValid() )
543 return;
544 }
545
546 if ( !f.hasGeometry() )
547 {
548 return;
549 }
550
551 QgsGeometry geom = f.geometry();
552
553 // scale or pan
554 if ( canvasExtent == Scale )
555 {
556 QgsRectangle featBBox = geom.boundingBox();
557 featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
558 QgsRectangle extent = mCanvas->extent();
559 if ( !extent.contains( featBBox ) )
560 {
561 extent.combineExtentWith( featBBox );
562 extent.scale( 1.1 );
563 mCanvas->setExtent( extent, true );
564 mCanvas->refresh();
565 }
566 }
567 else if ( canvasExtent == Pan )
568 {
569 QgsGeometry centroid = geom.centroid();
570 QgsPointXY center = centroid.asPoint();
571 center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
572 mCanvas->zoomByFactor( 1.0, &center ); // refresh is done in this method
573 }
574
575 // highlight
576 deleteHighlight();
577 mHighlight = new QgsHighlight( mCanvas, f, mReferencedLayer );
578 mHighlight->applyDefaultStyle();
579 mHighlight->show();
580
581 QTimer *timer = new QTimer( this );
582 timer->setSingleShot( true );
583 connect( timer, &QTimer::timeout, this, &QgsRelationReferenceWidget::deleteHighlight );
584 timer->start( 3000 );
585}
586
587void QgsRelationReferenceWidget::deleteHighlight()
588{
589 if ( mHighlight )
590 {
591 mHighlight->hide();
592 delete mHighlight;
593 }
594 mHighlight = nullptr;
595}
596
598{
599 if ( !mAllowMapIdentification || !mReferencedLayer )
600 return;
601
602 const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
603 if ( !tools )
604 return;
605 if ( !mCanvas )
606 return;
607
608 mMapToolIdentify->setLayer( mReferencedLayer );
609 setMapTool( mMapToolIdentify );
610
611 connect( mMapToolIdentify, qOverload<const QgsFeature &>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
612
613 if ( mMessageBar )
614 {
615 QString title = tr( "Relation %1 for %2." ).arg( mRelation.name(), mReferencingLayer->name() );
616 QString msg = tr( "Identify a feature of %1 to be associated. Press &lt;ESC&gt; to cancel." ).arg( mReferencedLayer->name() );
617 mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
618 mMessageBar->pushItem( mMessageBarItem );
619 }
620}
621
622void QgsRelationReferenceWidget::comboReferenceChanged()
623{
624 mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( mFeature );
625 highlightFeature( mFeature );
626 updateAttributeEditorFrame( mFeature );
627
628 emitForeignKeysChanged( mComboBox->identifierValues() );
629}
630
631void QgsRelationReferenceWidget::comboReferenceFoundChanged( bool )
632{
633 mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( mFeature );
634 highlightFeature( mFeature );
635 updateAttributeEditorFrame( mFeature );
636}
637
638void QgsRelationReferenceWidget::updateAttributeEditorFrame( const QgsFeature &feature )
639{
640 mOpenFormButton->setEnabled( feature.isValid() );
641 // Check if we're running with an embedded frame we need to update
642 if ( mAttributeEditorFrame && mReferencedAttributeForm )
643 {
644 mReferencedAttributeForm->setFeature( feature );
645 }
646}
647
649{
650 return mAllowAddFeatures;
651}
652
654{
655 mAllowAddFeatures = allowAddFeatures;
656 updateAddEntryButton();
657}
658
660{
661 return mRelation;
662}
663
664void QgsRelationReferenceWidget::featureIdentified( const QgsFeature &feature )
665{
666 mComboBox->setCurrentFeature( feature );
667 mFeature = feature;
668
669 mRemoveFKButton->setEnabled( mIsEditable );
670 highlightFeature( feature );
671 updateAttributeEditorFrame( feature );
672 emitForeignKeysChanged( foreignKeys(), true );
673
674 unsetMapTool();
675}
676
677void QgsRelationReferenceWidget::setMapTool( QgsMapTool *mapTool )
678{
679 mCurrentMapTool = mapTool;
680 mCanvas->setMapTool( mapTool );
681
682 mWindowWidget = window();
683
684 mCanvas->window()->raise();
685 mCanvas->activateWindow();
686 mCanvas->setFocus();
687 connect( mapTool, &QgsMapTool::deactivated, this, &QgsRelationReferenceWidget::mapToolDeactivated );
688}
689
690void QgsRelationReferenceWidget::unsetMapTool()
691{
692 // deactivate map tools if activated
693 if ( mCurrentMapTool )
694 {
695 /* this will call mapToolDeactivated */
696 mCanvas->unsetMapTool( mCurrentMapTool );
697
698 if ( mCurrentMapTool == mMapToolDigitize )
699 {
700 disconnect( mCanvas, &QgsMapCanvas::keyPressed, this, &QgsRelationReferenceWidget::onKeyPressed );
701 disconnect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationReferenceWidget::entryAdded );
702 }
703 else
704 {
705 disconnect( mMapToolIdentify, qOverload<const QgsFeature &>( &QgsMapToolIdentifyFeature::featureIdentified ), this, &QgsRelationReferenceWidget::featureIdentified );
706 }
707 }
708}
709
710void QgsRelationReferenceWidget::onKeyPressed( QKeyEvent *e )
711{
712 if ( e->key() == Qt::Key_Escape )
713 {
714 unsetMapTool();
715 }
716}
717
718void QgsRelationReferenceWidget::mapToolDeactivated()
719{
720 if ( mWindowWidget )
721 {
722 mWindowWidget->raise();
723 mWindowWidget->activateWindow();
724 }
725
726 if ( mMessageBar && mMessageBarItem )
727 {
728 mMessageBar->popWidget( mMessageBarItem );
729 }
730 mMessageBarItem = nullptr;
731}
732
733void QgsRelationReferenceWidget::filterChanged()
734{
735 QVariant nullValue = QgsApplication::nullRepresentation();
736
737 QMap<QString, QString> filters;
738 QgsAttributeList attrs;
739
740 QComboBox *scb = qobject_cast<QComboBox *>( sender() );
741
742 Q_ASSERT( scb );
743
744 QgsFeature f;
745 QgsFeatureIds featureIds;
746 QString filterExpression = mFilterExpression;
747
748 // wrap the expression with parentheses as it might contain `OR`
749 if ( !filterExpression.isEmpty() )
750 filterExpression = QStringLiteral( " ( %1 ) " ).arg( filterExpression );
751
752 // comboboxes have to be disabled before building filters
753 if ( mChainFilters )
754 disableChainedComboBoxes( scb );
755
756 // build filters
757 const auto constMFilterComboBoxes = mFilterComboBoxes;
758 for ( QComboBox *cb : constMFilterComboBoxes )
759 {
760 if ( cb->currentIndex() != 0 )
761 {
762 const QString fieldName = cb->property( "Field" ).toString();
763
764 if ( cb->currentText() == nullValue.toString() )
765 {
766 filters[fieldName] = QStringLiteral( "\"%1\" IS NULL" ).arg( fieldName );
767 }
768 else
769 {
770 filters[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, cb->currentText() );
771 }
772 attrs << mReferencedLayer->fields().lookupField( fieldName );
773 }
774 }
775
776 if ( mChainFilters )
777 {
778 QComboBox *ccb = nullptr;
779 const auto constMFilterComboBoxes = mFilterComboBoxes;
780 for ( QComboBox *cb : constMFilterComboBoxes )
781 {
782 if ( !ccb )
783 {
784 if ( cb == scb )
785 ccb = cb;
786
787 continue;
788 }
789
790 if ( ccb->currentIndex() != 0 )
791 {
792 const QString fieldName = cb->property( "Field" ).toString();
793
794 cb->blockSignals( true );
795 cb->clear();
796 cb->addItem( cb->property( "FieldAlias" ).toString() );
797
798 // ccb = scb
799 // cb = scb + 1
800 QStringList texts;
801 const auto txts { mFilterCache[ccb->property( "Field" ).toString()][ccb->currentText()] };
802 for ( const QString &txt : txts )
803 {
804 QMap<QString, QString> filtersAttrs = filters;
805 filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt );
806 QgsAttributeList subset = attrs;
807
808 QString expression = filterExpression;
809 if ( ! filterExpression.isEmpty() && ! filtersAttrs.isEmpty() )
810 expression += QLatin1String( " AND " );
811
812 expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral( " ( " );
813 expression += qgsMapJoinValues( filtersAttrs, QLatin1String( " AND " ) );
814 expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral( " ) " );
815
816 subset << mReferencedLayer->fields().lookupField( fieldName );
817
818 QgsFeatureIterator it( mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
819
820 bool found = false;
821 while ( it.nextFeature( f ) )
822 {
823 if ( !featureIds.contains( f.id() ) )
824 featureIds << f.id();
825
826 found = true;
827 }
828
829 // item is only provided if at least 1 feature exists
830 if ( found )
831 texts << txt;
832 }
833
834 texts.sort();
835 cb->addItems( texts );
836
837 cb->setEnabled( true );
838 cb->blockSignals( false );
839
840 ccb = cb;
841 }
842 }
843 }
844
845 if ( ! filterExpression.isEmpty() && ! filters.isEmpty() )
846 filterExpression += QLatin1String( " AND " );
847
848 filterExpression += filters.isEmpty() ? QString() : QStringLiteral( " ( " );
849 filterExpression += qgsMapJoinValues( filters, QLatin1String( " AND " ) );
850 filterExpression += filters.isEmpty() ? QString() : QStringLiteral( " ) " );
851
853}
854
855void QgsRelationReferenceWidget::addEntry()
856{
857 if ( !mReferencedLayer )
858 return;
859
860 const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
861 if ( !tools )
862 return;
863 if ( !mCanvas )
864 return;
865
866 // no geometry, skip the digitizing
867 if ( mReferencedLayer->geometryType() == Qgis::GeometryType::Unknown || mReferencedLayer->geometryType() == Qgis::GeometryType::Null )
868 {
869 QgsFeature f( mReferencedLayer->fields() );
870 entryAdded( f );
871 return;
872 }
873
874 mMapToolDigitize->setLayer( mReferencedLayer );
875 setMapTool( mMapToolDigitize );
876
877 connect( mMapToolDigitize, &QgsMapToolDigitizeFeature::digitizingCompleted, this, &QgsRelationReferenceWidget::entryAdded );
878 connect( mCanvas, &QgsMapCanvas::keyPressed, this, &QgsRelationReferenceWidget::onKeyPressed );
879
880 if ( mMessageBar )
881 {
882 QString title = tr( "Relation %1 for %2." ).arg( mRelation.name(), mReferencingLayer->name() );
883
884 QString displayString = QgsVectorLayerUtils::getFeatureDisplayString( mReferencingLayer, mFormFeature );
885 QString msg = tr( "Link feature to %1 \"%2\" : Digitize the geometry for the new feature on layer %3. Press &lt;ESC&gt; to cancel." )
886 .arg( mReferencingLayer->name(), displayString, mReferencedLayer->name() );
887 mMessageBarItem = QgsMessageBar::createMessage( title, msg, this );
888 mMessageBar->pushItem( mMessageBarItem );
889 }
890
891}
892
893void QgsRelationReferenceWidget::entryAdded( const QgsFeature &feat )
894{
895 QgsFeature f( feat );
896 QgsAttributeMap attributes;
897
898 // if custom text is in the combobox and the displayExpression is simply a field, use the current text for the new feature
899 if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
900 {
901 int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
902
903 if ( fieldIdx != -1 )
904 {
905 attributes.insert( fieldIdx, mComboBox->currentText() );
906 }
907 }
908
909 if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, f.geometry(), &f, this, false, true ) )
910 {
911 QVariantList attrs;
912 for ( const QString &fieldName : std::as_const( mReferencedFields ) )
913 attrs << f.attribute( fieldName );
914
915 setForeignKeys( attrs );
916
917 mAddEntryButton->setEnabled( false );
918 }
919
920 unsetMapTool();
921}
922
923void QgsRelationReferenceWidget::updateAddEntryButton()
924{
925 mAddEntryButton->setVisible( mAllowAddFeatures && mMapToolDigitize );
926 mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
927}
928
929void QgsRelationReferenceWidget::disableChainedComboBoxes( const QComboBox *scb )
930{
931 QComboBox *ccb = nullptr;
932 const auto constMFilterComboBoxes = mFilterComboBoxes;
933 for ( QComboBox *cb : constMFilterComboBoxes )
934 {
935 if ( !ccb )
936 {
937 if ( cb == scb )
938 {
939 ccb = cb;
940 }
941
942 continue;
943 }
944
945 cb->setCurrentIndex( 0 );
946 if ( ccb->currentIndex() == 0 )
947 {
948 cb->setEnabled( false );
949 }
950
951 ccb = cb;
952 }
953}
954
955void QgsRelationReferenceWidget::emitForeignKeysChanged( const QVariantList &foreignKeys, bool force )
956{
957 if ( foreignKeys == mForeignKeys && force == false && qVariantListIsNull( foreignKeys ) == qVariantListIsNull( mForeignKeys ) )
958 return;
959
960 mForeignKeys = foreignKeys;
962 emit foreignKeyChanged( foreignKeys.at( 0 ) );
965}
966
968{
969 return mReferencedLayerName;
970}
971
972void QgsRelationReferenceWidget::setReferencedLayerName( const QString &relationLayerName )
973{
974 mReferencedLayerName = relationLayerName;
975}
976
978{
979 return mReferencedLayerId;
980}
981
982void QgsRelationReferenceWidget::setReferencedLayerId( const QString &relationLayerId )
983{
984 mReferencedLayerId = relationLayerId;
985}
986
988{
989 return mReferencedLayerProviderKey;
990}
991
992void QgsRelationReferenceWidget::setReferencedLayerProviderKey( const QString &relationProviderKey )
993{
994 mReferencedLayerProviderKey = relationProviderKey;
995}
996
998{
999 return mReferencedLayerDataSource;
1000}
1001
1002void QgsRelationReferenceWidget::setReferencedLayerDataSource( const QString &relationDataSource )
1003{
1004 const QgsPathResolver resolver { QgsProject::instance()->pathResolver() };
1005 mReferencedLayerDataSource = resolver.writePath( relationDataSource );
1006}
1007
1009{
1010 mFormFeature = formFeature;
1011}
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, 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:79
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.
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.
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
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:120
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6042
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:5539
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6041
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:5369
QMap< int, QVariant > QgsAttributeMap
QSet< QgsFeatureId > QgsFeatureIds
QList< int > QgsAttributeList
Definition qgsfield.h:27
bool qVariantListIsNull(const QVariantList &list)