42#include "moc_qgsrelationreferencewidget.cpp"
44using namespace Qt::StringLiterals;
51 for (
int i = 0; i < list.size(); ++i )
63 mTopLayout =
new QVBoxLayout(
this );
64 mTopLayout->setContentsMargins( 0, 0, 0, 0 );
66 setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::Fixed );
68 setLayout( mTopLayout );
70 QHBoxLayout *editLayout =
new QHBoxLayout();
71 editLayout->setContentsMargins( 0, 0, 0, 0 );
72 editLayout->setSpacing( 2 );
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 );
87 mChooserContainer->layout()->addWidget( mComboBox );
90 mOpenFormButton =
new QToolButton();
92 mOpenFormButton->setText( tr(
"Open Related Feature Form" ) );
93 editLayout->addWidget( mOpenFormButton );
95 mAddEntryButton =
new QToolButton();
97 mAddEntryButton->setText( tr(
"Add New Entry" ) );
98 editLayout->addWidget( mAddEntryButton );
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 );
113 mMapIdentificationButton =
new QToolButton(
this );
115 mMapIdentificationButton->setText( tr(
"Select on Map" ) );
116 mMapIdentificationButton->setCheckable(
true );
117 editLayout->addWidget( mMapIdentificationButton );
120 mRemoveFKButton =
new QToolButton(
this );
122 mRemoveFKButton->setText( tr(
"No Selection" ) );
123 editLayout->addWidget( mRemoveFKButton );
126 mTopLayout->addLayout( editLayout );
130 mAttributeEditorLayout =
new QVBoxLayout( mAttributeEditorFrame );
131 mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
132 mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
133 mTopLayout->addWidget( mAttributeEditorFrame );
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 );
145 mMapIdentificationButton->hide();
146 mHighlightFeatureButton->hide();
147 mAttributeEditorFrame->hide();
148 mInvalidLabel->hide();
149 mAddEntryButton->hide();
153 connect( mHighlightFeatureButton, &QToolButton::triggered,
this, &QgsRelationReferenceWidget::highlightActionTriggered );
156 connect( mAddEntryButton, &QAbstractButton::clicked,
this, &QgsRelationReferenceWidget::addEntry );
157 connect( mComboBox, &QComboBox::editTextChanged,
this, &QgsRelationReferenceWidget::updateAddEntryButton );
168 mAllowNull = allowNullValue;
169 mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
173 mReferencedLayerId =
relation.referencedLayerId();
174 mReferencedLayerName =
relation.referencedLayer()->name();
176 mReferencedLayerProviderKey =
relation.referencedLayer()->providerType();
177 mInvalidLabel->hide();
180 mReferencingLayer =
relation.referencingLayer();
181 mReferencedLayer =
relation.referencedLayer();
183 const QList<QgsRelation::FieldPair> fieldPairs =
relation.fieldPairs();
186 mReferencedFields << fieldPair.referencedField();
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 );
198 mAttributeEditorFrame->setObjectName( u
"referencing/"_s +
relation.name() );
203 mAttributeEditorFrame->setTitle( mReferencedLayer->name() );
205 mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
210 updateAddEntryButton();
214 mInvalidLabel->show();
217 if ( mShown && isVisible() )
230 mFilterContainer->setEnabled( editable );
231 mComboBox->setEnabled( editable && !mReadOnlySelector );
232 mComboBox->setEditable(
true );
233 mMapIdentificationButton->setEnabled( editable );
234 mRemoveFKButton->setEnabled( editable );
235 mIsEditable = editable;
245 if ( values.isEmpty() )
255 if ( !mReferencedLayer )
258 mComboBox->setIdentifierValues( values );
260 if ( mEmbedForm || mChainFilters )
263 mReferencedLayer->getFeatures( request ).nextFeature( mFeature );
268 const int count = std::min( mFilterComboBoxes.size(), mFilterFields.size() );
269 for (
int i = 0; i < count; i++ )
271 QVariant v = mFeature.attribute( mFilterFields[i] );
273 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
277 mRemoveFKButton->setEnabled( mIsEditable );
278 highlightFeature( mFeature );
279 updateAttributeEditorFrame( mFeature );
287 if ( mChainFilters && !mFilterComboBoxes.isEmpty() )
289 QComboBox *cb = mFilterComboBoxes.first();
290 cb->setCurrentIndex( 0 );
291 disableChainedComboBoxes( cb );
294 mComboBox->setIdentifierValuesToNull();
295 mRemoveFKButton->setEnabled(
false );
307 return mReferencedAttributeForm->save();
313 if ( mReferencedLayer )
315 mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( f );
323 mRemoveFKButton->setEnabled(
false );
330 if ( fkeys.isEmpty() )
333 return fkeys.at( 0 );
338 return mComboBox->identifierValues();
343 mEditorContext = context;
345 mMessageBar = messageBar;
348 mMapToolIdentify->setButton( mMapIdentificationButton );
350 if ( mEditorContext.cadDockWidget() )
353 mMapToolDigitize->setButton( mAddEntryButton );
354 updateAddEntryButton();
362 setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
363 mTopLayout->setAlignment( Qt::AlignTop );
366 mAttributeEditorFrame->setVisible( display );
367 mEmbedForm = display;
372 mComboBox->setEnabled( !readOnly );
373 mRemoveFKButton->setVisible( mAllowNull && readOnly );
374 mReadOnlySelector = readOnly;
386 mFilterFields = filterFields;
402 mFilterExpression = expression;
416 if ( mReferencedLayer )
418 QApplication::setOverrideCursor( Qt::WaitCursor );
420 QSet<QString> requestedAttrs;
422 if ( !mFilterFields.isEmpty() )
424 for (
const QString &fieldName : std::as_const( mFilterFields ) )
426 int idx = mReferencedLayer->fields().lookupField( fieldName );
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 ) );
441 const auto constUniqueValues = uniqueValues;
442 for (
const QVariant &v : constUniqueValues )
444 cb->addItem( v.toString(), v );
447 connect( cb,
static_cast<void ( QComboBox::* )(
int )
>( &QComboBox::currentIndexChanged ),
this, &QgsRelationReferenceWidget::filterChanged );
450 requestedAttrs << fieldName;
452 mFilterLayout->addWidget( cb );
460 QgsFeatureIterator fit = mFilterExpression.isEmpty() ? mReferencedLayer->getFeatures() : mReferencedLayer->getFeatures( mFilterExpression );
463 const int count = std::min( mFilterComboBoxes.count(), mFilterFields.count() );
464 for (
int i = 0; i < count - 1; i++ )
466 QVariant cv = ft.
attribute( mFilterFields.at( i ) );
467 QVariant nv = ft.
attribute( mFilterFields.at( i + 1 ) );
470 mFilterCache[mFilterFields[i]][cf] << nf;
474 if ( !mFilterComboBoxes.isEmpty() )
476 QComboBox *cb = mFilterComboBoxes.first();
477 cb->setCurrentIndex( 0 );
478 disableChainedComboBoxes( cb );
484 mFilterContainer->hide();
487 mComboBox->setSourceLayer( mReferencedLayer );
488 mComboBox->setDisplayExpression( mReferencedLayer->displayExpression() );
489 mComboBox->setAllowNull( mAllowNull );
490 mComboBox->setIdentifierFields( mReferencedFields );
491 mComboBox->setFetchLimit( mFetchLimit );
492 mComboBox->setOrderExpression( mOrderExpression );
493 mComboBox->setSortOrder( mSortOrder );
495 if ( !mFilterExpression.isEmpty() )
496 mComboBox->setFilterExpression( mFilterExpression );
500 if ( mChainFilters && mFeature.isValid() )
502 for (
int i = 0; i < mFilterFields.size(); i++ )
504 QVariant v = mFeature.attribute( mFilterFields[i] );
506 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
515 QApplication::restoreOverrideCursor();
521void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
523 if ( action == mHighlightFeatureAction )
527 else if ( action == mScaleHighlightFeatureAction )
529 highlightFeature( QgsFeature(),
Scale );
531 else if ( action == mPanHighlightFeatureAction )
533 highlightFeature( QgsFeature(),
Pan );
546 attributeDialog->
show();
549void QgsRelationReferenceWidget::highlightFeature(
QgsFeature f, CanvasExtent canvasExtent )
569 if ( canvasExtent ==
Scale )
572 featBBox = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, featBBox );
573 QgsRectangle extent = mCanvas->extent();
578 mCanvas->setExtent( extent,
true );
582 else if ( canvasExtent ==
Pan )
584 QgsGeometry centroid = geom.
centroid();
585 QgsPointXY center = centroid.
asPoint();
586 center = mCanvas->mapSettings().layerToMapCoordinates( mReferencedLayer, center );
587 mCanvas->zoomByFactor( 1.0, ¢er );
592 mHighlight =
new QgsHighlight( mCanvas, f, mReferencedLayer );
593 mHighlight->applyDefaultStyle();
596 QTimer *timer =
new QTimer(
this );
597 timer->setSingleShot(
true );
598 connect( timer, &QTimer::timeout,
this, &QgsRelationReferenceWidget::deleteHighlight );
599 timer->start( 3000 );
602void QgsRelationReferenceWidget::deleteHighlight()
609 mHighlight =
nullptr;
614 if ( !mAllowMapIdentification || !mReferencedLayer )
623 mMapToolIdentify->setLayer( mReferencedLayer );
624 setMapTool( mMapToolIdentify );
630 QString title = tr(
"Relation %1 for %2." ).arg( mRelation.name(), mReferencingLayer->name() );
631 QString msg = tr(
"Identify a feature of %1 to be associated. Press <ESC> to cancel." ).arg( mReferencedLayer->name() );
633 mMessageBar->pushItem( mMessageBarItem );
637void QgsRelationReferenceWidget::comboReferenceChanged()
640 highlightFeature( mFeature );
641 updateAttributeEditorFrame( mFeature );
646void QgsRelationReferenceWidget::comboReferenceFoundChanged(
bool )
648 mReferencedLayer->getFeatures( mComboBox->currentFeatureRequest() ).nextFeature( mFeature );
649 highlightFeature( mFeature );
650 updateAttributeEditorFrame( mFeature );
653void QgsRelationReferenceWidget::updateAttributeEditorFrame(
const QgsFeature &feature )
655 mOpenFormButton->setEnabled( feature.
isValid() );
657 if ( mAttributeEditorFrame && mReferencedAttributeForm )
659 mReferencedAttributeForm->setFeature( feature );
665 return mAllowAddFeatures;
671 updateAddEntryButton();
679void QgsRelationReferenceWidget::featureIdentified(
const QgsFeature &feature )
684 mRemoveFKButton->setEnabled( mIsEditable );
685 highlightFeature( feature );
686 updateAttributeEditorFrame( feature );
692void QgsRelationReferenceWidget::setMapTool(
QgsMapTool *mapTool )
694 mCurrentMapTool = mapTool;
695 mCanvas->setMapTool( mapTool );
697 mWindowWidget = window();
699 mCanvas->window()->raise();
700 mCanvas->activateWindow();
705void QgsRelationReferenceWidget::unsetMapTool()
708 if ( mCurrentMapTool )
711 mCanvas->unsetMapTool( mCurrentMapTool );
713 if ( mCurrentMapTool == mMapToolDigitize )
725void QgsRelationReferenceWidget::onKeyPressed( QKeyEvent *e )
727 if ( e->key() == Qt::Key_Escape )
733void QgsRelationReferenceWidget::mapToolDeactivated()
737 mWindowWidget->raise();
738 mWindowWidget->activateWindow();
741 if ( mMessageBar && mMessageBarItem )
743 mMessageBar->popWidget( mMessageBarItem );
745 mMessageBarItem =
nullptr;
748void QgsRelationReferenceWidget::filterChanged()
752 QMap<QString, QString> filters;
755 QComboBox *scb = qobject_cast<QComboBox *>( sender() );
769 disableChainedComboBoxes( scb );
772 const auto constMFilterComboBoxes = mFilterComboBoxes;
773 for ( QComboBox *cb : constMFilterComboBoxes )
775 if ( cb->currentIndex() != 0 )
777 const QString fieldName = cb->property(
"Field" ).toString();
779 if ( cb->currentText() == nullValue.toString() )
781 filters[fieldName] = u
"\"%1\" IS NULL"_s.arg( fieldName );
787 attrs << mReferencedLayer->fields().lookupField( fieldName );
793 QComboBox *ccb =
nullptr;
794 const auto constMFilterComboBoxes = mFilterComboBoxes;
795 for ( QComboBox *cb : constMFilterComboBoxes )
805 if ( ccb->currentIndex() != 0 )
807 const QString fieldName = cb->property(
"Field" ).toString();
809 cb->blockSignals(
true );
811 cb->addItem( cb->property(
"FieldAlias" ).toString() );
816 const auto txts { mFilterCache[ccb->property(
"Field" ).toString()][ccb->currentText()] };
817 for (
const QString &txt : txts )
819 QMap<QString, QString> filtersAttrs = filters;
824 if ( !expression.isEmpty() && !filtersAttrs.isEmpty() )
825 expression +=
" AND "_L1;
827 expression += filtersAttrs.isEmpty() ? QString() : u
" ( "_s;
829 expression += filtersAttrs.isEmpty() ? QString() : u
" ) "_s;
831 subset << mReferencedLayer->fields().lookupField( fieldName );
833 QgsFeatureIterator it( mReferencedLayer->getFeatures( QgsFeatureRequest().
setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
836 while ( it.nextFeature( f ) )
838 if ( !featureIds.contains( f.
id() ) )
839 featureIds << f.
id();
850 cb->addItems( texts );
852 cb->setEnabled(
true );
853 cb->blockSignals(
false );
870void QgsRelationReferenceWidget::addEntry()
872 if ( !mReferencedLayer )
875 const QgsVectorLayerTools *tools = mEditorContext.vectorLayerTools();
884 QgsFeature f( mReferencedLayer->fields() );
889 mMapToolDigitize->setLayer( mReferencedLayer );
890 setMapTool( mMapToolDigitize );
897 QString title = tr(
"Relation %1 for %2." ).arg( mRelation.name(), mReferencingLayer->name() );
901 = tr(
"Link feature to %1 \"%2\" : Digitize the geometry for the new feature on layer %3. Press <ESC> to cancel." ).arg( mReferencingLayer->name(), displayString, mReferencedLayer->name() );
903 mMessageBar->pushItem( mMessageBarItem );
907void QgsRelationReferenceWidget::entryAdded(
const QgsFeature &feat )
909 QgsFeature f( feat );
913 if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
915 int fieldIdx = mReferencedLayer->fields().lookupField( mReferencedLayer->displayExpression() );
917 if ( fieldIdx != -1 )
919 attributes.insert( fieldIdx, mComboBox->currentText() );
923 if ( mEditorContext.vectorLayerTools()->addFeature( mReferencedLayer, attributes, f.
geometry(), &f,
this,
false,
true ) )
926 for (
const QString &fieldName : std::as_const( mReferencedFields ) )
931 mAddEntryButton->setEnabled(
false );
937void QgsRelationReferenceWidget::updateAddEntryButton()
939 mAddEntryButton->setVisible( mAllowAddFeatures && mMapToolDigitize );
940 mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->isEditable() );
943void QgsRelationReferenceWidget::disableChainedComboBoxes(
const QComboBox *scb )
945 QComboBox *ccb =
nullptr;
946 const auto constMFilterComboBoxes = mFilterComboBoxes;
947 for ( QComboBox *cb : constMFilterComboBoxes )
959 cb->setCurrentIndex( 0 );
960 if ( ccb->currentIndex() == 0 )
962 cb->setEnabled(
false );
969void QgsRelationReferenceWidget::emitForeignKeysChanged(
const QVariantList &foreignKeys,
bool force )
983 return mReferencedLayerName;
988 mReferencedLayerName = relationLayerName;
993 return mReferencedLayerId;
998 mReferencedLayerId = relationLayerId;
1003 return mReferencedLayerProviderKey;
1008 mReferencedLayerProviderKey = relationProviderKey;
1013 return mReferencedLayerDataSource;
1019 mReferencedLayerDataSource = resolver.
writePath( relationDataSource );
1024 mComboBox->setFormFeature( formFeature );
1029 mComboBox->setParentFormFeature( parentFormFeature );
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.
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.
QVariantList identifierValues
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...
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.
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.
Defines a relation between matching fields of the two involved tables of a relation.
Represents a relationship between two vector layers.
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.
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.
#define Q_NOWARN_DEPRECATED_POP
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.
#define Q_NOWARN_DEPRECATED_PUSH
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
QMap< int, QVariant > QgsAttributeMap
QSet< QgsFeatureId > QgsFeatureIds
QList< int > QgsAttributeList