18 #include <QPushButton> 20 #include <QHBoxLayout> 51 mTopLayout =
new QVBoxLayout(
this );
52 mTopLayout->setContentsMargins( 0, 0, 0, 0 );
54 setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::Fixed );
56 setLayout( mTopLayout );
58 QHBoxLayout *editLayout =
new QHBoxLayout();
59 editLayout->setContentsMargins( 0, 0, 0, 0 );
60 editLayout->setSpacing( 2 );
63 mChooserContainer =
new QWidget;
64 editLayout->addWidget( mChooserContainer );
65 QHBoxLayout *chooserLayout =
new QHBoxLayout;
66 chooserLayout->setContentsMargins( 0, 0, 0, 0 );
67 mFilterLayout =
new QHBoxLayout;
68 mFilterLayout->setContentsMargins( 0, 0, 0, 0 );
69 mFilterContainer =
new QWidget;
70 mFilterContainer->setLayout( mFilterLayout );
71 mChooserContainer->setLayout( chooserLayout );
72 chooserLayout->addWidget( mFilterContainer );
75 mChooserContainer->layout()->addWidget( mComboBox );
78 mLineEdit =
new QLineEdit();
79 mLineEdit->setReadOnly(
true );
80 editLayout->addWidget( mLineEdit );
83 mOpenFormButton =
new QToolButton();
85 mOpenFormButton->setText( tr(
"Open Related Feature Form" ) );
86 editLayout->addWidget( mOpenFormButton );
88 mAddEntryButton =
new QToolButton();
90 mAddEntryButton->setText( tr(
"Add New Entry" ) );
91 editLayout->addWidget( mAddEntryButton );
94 mHighlightFeatureButton =
new QToolButton(
this );
95 mHighlightFeatureButton->setPopupMode( QToolButton::MenuButtonPopup );
96 mHighlightFeatureAction =
new QAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/mActionHighlightFeature.svg" ) ), tr(
"Highlight feature" ),
this );
97 mScaleHighlightFeatureAction =
new QAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/mActionScaleHighlightFeature.svg" ) ), tr(
"Scale and highlight feature" ),
this );
98 mPanHighlightFeatureAction =
new QAction(
QgsApplication::getThemeIcon( QStringLiteral(
"/mActionPanHighlightFeature.svg" ) ), tr(
"Pan and highlight feature" ),
this );
99 mHighlightFeatureButton->addAction( mHighlightFeatureAction );
100 mHighlightFeatureButton->addAction( mScaleHighlightFeatureAction );
101 mHighlightFeatureButton->addAction( mPanHighlightFeatureAction );
102 mHighlightFeatureButton->setDefaultAction( mHighlightFeatureAction );
103 editLayout->addWidget( mHighlightFeatureButton );
106 mMapIdentificationButton =
new QToolButton(
this );
108 mMapIdentificationButton->setText( tr(
"Select on Map" ) );
109 mMapIdentificationButton->setCheckable(
true );
110 editLayout->addWidget( mMapIdentificationButton );
113 mRemoveFKButton =
new QToolButton(
this );
115 mRemoveFKButton->setText( tr(
"No Selection" ) );
116 editLayout->addWidget( mRemoveFKButton );
119 mTopLayout->addLayout( editLayout );
123 mAttributeEditorLayout =
new QVBoxLayout( mAttributeEditorFrame );
124 mAttributeEditorFrame->setLayout( mAttributeEditorLayout );
125 mAttributeEditorFrame->setSizePolicy( mAttributeEditorFrame->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding );
126 mTopLayout->addWidget( mAttributeEditorFrame );
129 mInvalidLabel =
new QLabel( tr(
"The relation is not valid. Please make sure your relation definitions are OK." ) );
130 mInvalidLabel->setWordWrap(
true );
131 QFont font = mInvalidLabel->font();
132 font.setItalic(
true );
133 mInvalidLabel->setStyleSheet( QStringLiteral(
"QLabel { color: red; } " ) );
134 mInvalidLabel->setFont( font );
135 mTopLayout->addWidget( mInvalidLabel );
139 mMapIdentificationButton->hide();
140 mHighlightFeatureButton->hide();
141 mAttributeEditorFrame->hide();
142 mInvalidLabel->hide();
146 connect( mHighlightFeatureButton, &QToolButton::triggered,
this, &QgsRelationReferenceWidget::highlightActionTriggered );
149 connect( mAddEntryButton, &QAbstractButton::clicked,
this, &QgsRelationReferenceWidget::addEntry );
150 connect( mComboBox, &QComboBox::editTextChanged,
this, &QgsRelationReferenceWidget::updateAddEntryButton );
161 void QgsRelationReferenceWidget::updateIndex()
163 if ( mChainFilters && mComboBox->count() > 0 )
168 if ( ! mFilterComboBoxes.isEmpty()
169 && mFilterComboBoxes[0]->currentIndex() == 0 && mAllowNull )
173 else if ( mComboBox->count() > mComboBox->
nullIndex() )
177 else if ( mAllowNull )
186 if ( mComboBox->count() > index )
188 mComboBox->setCurrentIndex( index );
195 mAllowNull = allowNullValue;
196 mRemoveFKButton->setVisible( allowNullValue && mReadOnlySelector );
200 mInvalidLabel->hide();
202 mRelation = relation;
204 mRelationName = relation.
name();
206 mReferencedField = relation.
fieldPairs().at( 0 ).second;
212 mAttributeEditorFrame->setObjectName( QStringLiteral(
"referencing/" ) + relation.
name() );
218 mAttributeEditorFrame->setTitle( mReferencedLayer->
name() );
220 mAttributeEditorLayout->addWidget( mReferencedAttributeForm );
225 updateAddEntryButton();
229 mInvalidLabel->show();
232 if ( mShown && isVisible() )
243 mFilterContainer->setEnabled( editable );
244 mComboBox->setEnabled( editable );
245 mComboBox->setEditable(
true );
246 mMapIdentificationButton->setEnabled( editable );
247 mRemoveFKButton->setEnabled( editable );
248 mIsEditable = editable;
253 if ( !value.isValid() )
257 if ( value.isNull() )
263 if ( !mReferencedLayer )
266 if ( mReadOnlySelector )
282 mForeignKey = mFeature.
attribute( mReferencedFieldIdx );
286 context.setFeature( mFeature );
287 QString title = expr.evaluate( &context ).toString();
288 if ( expr.hasEvalError() )
290 title = mFeature.
attribute( mReferencedFieldIdx ).toString();
292 mLineEdit->setText( title );
306 const int count = std::min( mFilterComboBoxes.size(), mFilterFields.size() );
307 for (
int i = 0; i < count; i++ )
309 QVariant v = mFeature.
attribute( mFilterFields[i] );
310 QString f = v.isNull() ? nullValue.toString() : v.toString();
311 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
316 mRemoveFKButton->setEnabled( mIsEditable );
317 highlightFeature( mFeature );
318 updateAttributeEditorFrame( mFeature );
326 if ( mChainFilters && !mFilterComboBoxes.isEmpty() )
328 QComboBox *cb = mFilterComboBoxes.first();
329 cb->setCurrentIndex( 0 );
330 disableChainedComboBoxes( cb );
333 if ( mReadOnlySelector )
340 nullText = tr(
"%1 (no selection)" ).arg( nullValue );
342 mLineEdit->setText( nullText );
343 mForeignKey = QVariant( QVariant::Int );
350 mRemoveFKButton->setEnabled(
false );
352 emitForeignKeyChanged( QVariant( QVariant::Int ) );
358 if ( mReferencedLayer )
361 if ( mReadOnlySelector )
376 if ( mReadOnlySelector )
382 whileBlocking( mComboBox )->setIdentifierValue( QVariant() );
384 mRemoveFKButton->setEnabled(
false );
390 if ( mReadOnlySelector )
402 mEditorContext = context;
404 mMessageBar = messageBar;
408 mMapTool->
setButton( mMapIdentificationButton );
415 setSizePolicy( sizePolicy().horizontalPolicy(), QSizePolicy::MinimumExpanding );
416 mTopLayout->setAlignment( Qt::AlignTop );
419 mAttributeEditorFrame->setVisible( display );
420 mEmbedForm = display;
425 mChooserContainer->setHidden( readOnly );
426 mLineEdit->setVisible( readOnly );
427 mRemoveFKButton->setVisible( mAllowNull && readOnly );
428 mReadOnlySelector = readOnly;
433 mHighlightFeatureButton->setVisible( allowMapIdentification );
434 mMapIdentificationButton->setVisible( allowMapIdentification );
445 mFilterFields = filterFields;
450 mOpenFormButton->setVisible( openFormButtonVisible );
470 if ( !mReadOnlySelector && mReferencedLayer )
472 QApplication::setOverrideCursor( Qt::WaitCursor );
474 QSet<QString> requestedAttrs;
476 if ( !mFilterFields.isEmpty() )
478 for (
const QString &fieldName : qgis::as_const( mFilterFields ) )
485 QComboBox *cb =
new QComboBox();
486 cb->setProperty(
"Field", fieldName );
488 mFilterComboBoxes << cb;
489 QVariantList uniqueValues = mReferencedLayer->
uniqueValues( idx ).toList();
492 cb->addItem( nullValue.toString(), QVariant( mReferencedLayer->
fields().
at( idx ).
type() ) );
495 const auto constUniqueValues = uniqueValues;
496 for (
const QVariant &v : constUniqueValues )
498 cb->addItem( v.toString(), v );
501 connect( cb,
static_cast<void ( QComboBox::* )(
int )
>( &QComboBox::currentIndexChanged ),
this, &QgsRelationReferenceWidget::filterChanged );
504 requestedAttrs << fieldName;
506 mFilterLayout->addWidget( cb );
517 const int count = std::min( mFilterComboBoxes.count(), mFilterFields.count() );
518 for (
int i = 0; i < count - 1; i++ )
520 QVariant cv = ft.
attribute( mFilterFields.at( i ) );
521 QVariant nv = ft.
attribute( mFilterFields.at( i + 1 ) );
522 QString cf = cv.isNull() ? nullValue.toString() : cv.toString();
523 QString nf = nv.isNull() ? nullValue.toString() : nv.toString();
524 mFilterCache[mFilterFields[i]][cf] << nf;
528 if ( !mFilterComboBoxes.isEmpty() )
530 QComboBox *cb = mFilterComboBoxes.first();
531 cb->setCurrentIndex( 0 );
532 disableChainedComboBoxes( cb );
538 mFilterContainer->hide();
548 if ( mChainFilters && mFeature.
isValid() )
550 for (
int i = 0; i < mFilterFields.size(); i++ )
552 QVariant v = mFeature.
attribute( mFilterFields[i] );
553 QString f = v.isNull() ? nullValue.toString() : v.toString();
554 mFilterComboBoxes.at( i )->setCurrentIndex( mFilterComboBoxes.at( i )->findText( f ) );
559 connect( mComboBox,
static_cast<void ( QComboBox::* )(
int )
>( &QComboBox::currentIndexChanged ),
this, &QgsRelationReferenceWidget::comboReferenceChanged );
561 emit mComboBox->currentIndexChanged( mComboBox->currentIndex() );
563 QApplication::restoreOverrideCursor();
569 void QgsRelationReferenceWidget::highlightActionTriggered( QAction *action )
571 if ( action == mHighlightFeatureAction )
575 else if ( action == mScaleHighlightFeatureAction )
579 else if ( action == mPanHighlightFeatureAction )
594 attributeDialog.exec();
617 if ( canvasExtent ==
Scale )
630 else if ( canvasExtent ==
Pan )
640 mHighlight =
new QgsHighlight( mCanvas, f, mReferencedLayer );
644 QTimer *timer =
new QTimer(
this );
645 timer->setSingleShot(
true );
646 connect( timer, &QTimer::timeout,
this, &QgsRelationReferenceWidget::deleteHighlight );
647 timer->start( 3000 );
650 void QgsRelationReferenceWidget::deleteHighlight()
657 mHighlight =
nullptr;
662 if ( !mAllowMapIdentification || !mReferencedLayer )
671 mMapTool->
setLayer( mReferencedLayer );
674 mWindowWidget = window();
676 mCanvas->window()->raise();
677 mCanvas->activateWindow();
685 QString title = tr(
"Relation %1 for %2." ).arg( mRelationName, mReferencingLayer->
name() );
686 QString msg = tr(
"Identify a feature of %1 to be associated. Press <ESC> to cancel." ).arg( mReferencedLayer->
name() );
688 mMessageBar->
pushItem( mMessageBarItem );
692 void QgsRelationReferenceWidget::comboReferenceChanged(
int index )
696 highlightFeature( mFeature );
697 updateAttributeEditorFrame( mFeature );
702 void QgsRelationReferenceWidget::updateAttributeEditorFrame(
const QgsFeature &feature )
704 mOpenFormButton->setEnabled( feature.
isValid() );
706 if ( mAttributeEditorFrame && mReferencedAttributeForm )
708 mReferencedAttributeForm->
setFeature( feature );
714 return mAllowAddFeatures;
720 updateAddEntryButton();
723 void QgsRelationReferenceWidget::featureIdentified(
const QgsFeature &feature )
725 if ( mReadOnlySelector )
729 context.setFeature( feature );
730 QString title = expr.evaluate( &context ).toString();
731 if ( expr.hasEvalError() )
733 title = feature.
attribute( mReferencedFieldIdx ).toString();
735 mLineEdit->setText( title );
736 mForeignKey = feature.
attribute( mReferencedFieldIdx );
741 mComboBox->setCurrentIndex( mComboBox->findData( feature.
attribute( mReferencedFieldIdx ), QgsFeatureFilterModel::Role::IdentifierValueRole ) );
745 mRemoveFKButton->setEnabled( mIsEditable );
746 highlightFeature( feature );
747 updateAttributeEditorFrame( feature );
753 void QgsRelationReferenceWidget::unsetMapTool()
756 if ( mCanvas && mMapTool )
763 void QgsRelationReferenceWidget::mapToolDeactivated()
767 mWindowWidget->raise();
768 mWindowWidget->activateWindow();
771 if ( mMessageBar && mMessageBarItem )
773 mMessageBar->
popWidget( mMessageBarItem );
775 mMessageBarItem =
nullptr;
778 void QgsRelationReferenceWidget::filterChanged()
782 QMap<QString, QString> filters;
785 QComboBox *scb = qobject_cast<QComboBox *>( sender() );
791 QString filterExpression;
795 disableChainedComboBoxes( scb );
798 const auto constMFilterComboBoxes = mFilterComboBoxes;
799 for ( QComboBox *cb : constMFilterComboBoxes )
801 if ( cb->currentIndex() != 0 )
803 const QString fieldName = cb->property(
"Field" ).toString();
805 if ( cb->currentText() == nullValue.toString() )
807 filters[fieldName] = QStringLiteral(
"\"%1\" IS NULL" ).arg( fieldName );
819 QComboBox *ccb =
nullptr;
820 const auto constMFilterComboBoxes = mFilterComboBoxes;
821 for ( QComboBox *cb : constMFilterComboBoxes )
831 if ( ccb->currentIndex() != 0 )
833 const QString fieldName = cb->property(
"Field" ).toString();
835 cb->blockSignals(
true );
837 cb->addItem( cb->property(
"FieldAlias" ).toString() );
842 const auto txts { mFilterCache[ccb->property(
"Field" ).toString()][ccb->currentText()] };
843 for (
const QString &txt : txts )
845 QMap<QString, QString> filtersAttrs = filters;
847 QString expression = filtersAttrs.values().join( QStringLiteral(
" AND " ) );
855 while ( it.nextFeature( f ) )
857 if ( !featureIds.contains( f.
id() ) )
858 featureIds << f.
id();
869 cb->addItems( texts );
871 cb->setEnabled(
true );
872 cb->blockSignals(
false );
878 filterExpression = filters.values().join( QStringLiteral(
" AND " ) );
882 void QgsRelationReferenceWidget::addEntry()
888 if ( mComboBox->itemText( mComboBox->currentIndex() ) != mComboBox->currentText() )
892 if ( fieldIdx != -1 )
894 attributes.insert( fieldIdx, mComboBox->currentText() );
901 mAddEntryButton->setEnabled(
false );
905 void QgsRelationReferenceWidget::updateAddEntryButton()
907 mAddEntryButton->setVisible( mAllowAddFeatures );
908 mAddEntryButton->setEnabled( mReferencedLayer && mReferencedLayer->
isEditable() );
911 void QgsRelationReferenceWidget::disableChainedComboBoxes(
const QComboBox *scb )
913 QComboBox *ccb =
nullptr;
914 const auto constMFilterComboBoxes = mFilterComboBoxes;
915 for ( QComboBox *cb : constMFilterComboBoxes )
927 cb->setCurrentIndex( 0 );
928 if ( ccb->currentIndex() == 0 )
930 cb->setEnabled(
false );
937 void QgsRelationReferenceWidget::emitForeignKeyChanged(
const QVariant &
foreignKey )
939 if ( foreignKey != mForeignKey || foreignKey.isNull() != mForeignKey.isNull() )
void unsetMapTool(QgsMapTool *mapTool)
Unset the current map tool or last non zoom tool.
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
When showing a single feature (e.g. district information when looking at the form of a house) ...
bool isValid() const
Returns the validity of this feature.
Class for parsing and evaluation of expressions (formerly called "search strings").
void setIdentifierValue(const QVariant &identifierValue)
The identifier value of the currently selected feature.
const QgsVectorLayerTools * vectorLayerTools() const
Returns the associated vector layer tools.
Wrapper for iterator of features from vector data provider or vector layer.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
This offers a combobox with autocompleter that allows selecting features from a layer.
A rectangle specified with double values.
QgsFeatureRequest currentFeatureRequest() const
Shorthand for getting a feature request to query the currently selected feature.
QSet< QgsFeatureId > QgsFeatureIds
int nullIndex() const
Returns the current index of the NULL value, or -1 if NULL values are not allowed.
A form was opened as a new dialog.
A groupbox that collapses/expands when toggled and can save its collapsed and checked states...
This class contains context information for attribute editor widgets.
A class to represent a 2D point.
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
QgsVectorLayer referencingLayer
A bar for displaying non-blocking messages to the user.
void refresh()
Repaints the canvas map.
A geometry is the spatial representation of a feature.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
void setSourceLayer(QgsVectorLayer *sourceLayer)
The layer from which features should be listed.
bool hasGeometry() const
Returns true if the feature has an associated geometry.
int count() const
Returns number of items.
Map canvas is a class for displaying all GIS data types on a canvas.
bool qgsVariantLessThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is less than the second.
QgsField at(int i) const
Gets field at particular index (must be in range 0..N-1)
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
QgsFeatureRequest & setFilterFid(QgsFeatureId fid)
Sets feature ID that should be fetched.
static QgsMessageBarItem * createMessage(const QString &text, QWidget *parent=nullptr)
make out a widget containing a message to be displayed on the bar
void setMapTool(QgsMapTool *mapTool, bool clean=false)
Sets the map tool currently being used on the canvas.
QgsFeatureRequest getReferencedFeatureRequest(const QgsAttributes &attributes) const
Creates a request to return the feature on the referenced (parent) layer which is referenced by the p...
QgsFields fields() const FINAL
Returns the list of fields of this layer.
bool popWidget(QgsMessageBarItem *item)
Remove the passed widget from the bar (if previously added), then display the next one in the stack i...
void setAllowNull(bool allowNull)
Determines if a NULL value should be available in the list.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QMap< int, QVariant > QgsAttributeMap
QgsRectangle extent() const
Returns the current zoom extent of the map canvas.
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
A class for highlight features on the map.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
static QString nullRepresentation()
This string is used to represent the value NULL throughout QGIS.
QgsVectorLayer referencedLayer
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
void editingStarted()
Emitted when editing on this layer has started.
static QString createFieldEqualityExpression(const QString &fieldName, const QVariant &value)
Create an expression allowing to evaluate if a field is equal to a value.
QString displayExpression
void zoomByFactor(double scaleFactor, const QgsPointXY *center=nullptr)
Zoom with the factor supplied.
QList< QgsRelation::FieldPair > fieldPairs() const
Returns the field pairs which form this relation The first element of each pair are the field names o...
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle...
void setFilterExpression(const QString &filterExpression)
An additional expression to further restrict the available features.
void setValid(bool validity)
Sets the validity of the feature.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
void pushItem(QgsMessageBarItem *item)
Display a message item on the bar after hiding the currently visible one and putting it in a stack...
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
QSet< QVariant > uniqueValues(int fieldIndex, int limit=-1) const FINAL
Calculates a list of unique values contained within an attribute in the layer.
QgsPointXY layerToMapCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from layer's CRS to output CRS
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
void setExtent(const QgsRectangle &r, bool magnified=false)
Sets the extent of the map canvas.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QList< int > QgsAttributeList
bool nextFeature(QgsFeature &f)
void setDisplayExpression(const QString &displayExpression)
The display expression will be used to display features as well as the value to match the typed text ...
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
QString attributeDisplayName(int index) const
Convenience function that returns the attribute alias if defined or the field name else...
void modelUpdated()
The underlying model has been updated.
void setIdentifierField(const QString &identifierField)
Field name that will be used to uniquely identify the current feature.
A form was embedded as a widget on another form.