mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-25 00:58:06 -05:00
Merge pull request #38659 from suricactus/relref_filter_expr
Add support for filter expressions in relation reference widget
This commit is contained in:
commit
a853f2643d
@ -136,6 +136,16 @@ Set if filters are chained.
|
||||
Chained filters restrict the option of subsequent filters based on the selection of a previous filter.
|
||||
|
||||
:param chainFilters: If chaining should be enabled
|
||||
%End
|
||||
|
||||
QString filterExpression() const;
|
||||
%Docstring
|
||||
Returns the currently set filter expression.
|
||||
%End
|
||||
void setFilterExpression( const QString &filterExpression );
|
||||
%Docstring
|
||||
If not empty, will be used as filter expression.
|
||||
Only if this evaluates to ``True``, the value will be shown.
|
||||
%End
|
||||
|
||||
QgsFeature referencedFeature() const;
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "qgsrelationmanager.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgsexpressionbuilderdialog.h"
|
||||
#include "qgsexpressioncontextutils.h"
|
||||
|
||||
QgsRelationReferenceConfigDlg::QgsRelationReferenceConfigDlg( QgsVectorLayer *vl, int fieldIdx, QWidget *parent )
|
||||
: QgsEditorConfigWidget( vl, fieldIdx, parent )
|
||||
@ -59,6 +60,34 @@ QgsRelationReferenceConfigDlg::QgsRelationReferenceConfigDlg( QgsVectorLayer *vl
|
||||
connect( mFilterFieldsList, &QListWidget::itemChanged, this, &QgsEditorConfigWidget::changed );
|
||||
connect( mCbxChainFilters, &QAbstractButton::toggled, this, &QgsEditorConfigWidget::changed );
|
||||
connect( mExpressionWidget, static_cast<void ( QgsFieldExpressionWidget::* )( const QString & )>( &QgsFieldExpressionWidget::fieldChanged ), this, &QgsEditorConfigWidget::changed );
|
||||
connect( mEditExpression, &QAbstractButton::clicked, this, &QgsRelationReferenceConfigDlg::mEditExpression_clicked );
|
||||
connect( mFilterExpression, &QTextEdit::textChanged, this, &QgsEditorConfigWidget::changed );
|
||||
}
|
||||
|
||||
void QgsRelationReferenceConfigDlg::mEditExpression_clicked()
|
||||
{
|
||||
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer() );
|
||||
if ( !vl )
|
||||
return;
|
||||
|
||||
QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( vl ) );
|
||||
context << QgsExpressionContextUtils::formScope( );
|
||||
context << QgsExpressionContextUtils::parentFormScope( );
|
||||
|
||||
context.setHighlightedFunctions( QStringList() << QStringLiteral( "current_value" ) << QStringLiteral( "current_parent_value" ) );
|
||||
context.setHighlightedVariables( QStringList() << QStringLiteral( "current_geometry" )
|
||||
<< QStringLiteral( "current_feature" )
|
||||
<< QStringLiteral( "form_mode" )
|
||||
<< QStringLiteral( "current_parent_geometry" )
|
||||
<< QStringLiteral( "current_parent_feature" ) );
|
||||
|
||||
QgsExpressionBuilderDialog dlg( vl, mFilterExpression->toPlainText(), this, QStringLiteral( "generic" ), context );
|
||||
dlg.setWindowTitle( tr( "Edit Filter Expression" ) );
|
||||
|
||||
if ( dlg.exec() == QDialog::Accepted )
|
||||
{
|
||||
mFilterExpression->setPlainText( dlg.expressionBuilder()->expressionText() );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsRelationReferenceConfigDlg::setConfig( const QVariantMap &config )
|
||||
@ -77,6 +106,7 @@ void QgsRelationReferenceConfigDlg::setConfig( const QVariantMap &config )
|
||||
mCbxMapIdentification->setChecked( config.value( QStringLiteral( "MapIdentification" ), false ).toBool() );
|
||||
mCbxAllowAddFeatures->setChecked( config.value( QStringLiteral( "AllowAddFeatures" ), false ).toBool() );
|
||||
mCbxReadOnly->setChecked( config.value( QStringLiteral( "ReadOnly" ), false ).toBool() );
|
||||
mFilterExpression->setPlainText( config.value( QStringLiteral( "FilterExpression" ) ).toString() );
|
||||
|
||||
if ( config.contains( QStringLiteral( "FilterFields" ) ) )
|
||||
{
|
||||
@ -149,6 +179,7 @@ QVariantMap QgsRelationReferenceConfigDlg::config()
|
||||
myConfig.insert( QStringLiteral( "FilterFields" ), filterFields );
|
||||
|
||||
myConfig.insert( QStringLiteral( "ChainFilters" ), mCbxChainFilters->isChecked() );
|
||||
myConfig.insert( QStringLiteral( "FilterExpression" ), mFilterExpression->toPlainText() );
|
||||
}
|
||||
|
||||
if ( mReferencedLayer )
|
||||
|
@ -51,6 +51,11 @@ class GUI_EXPORT QgsRelationReferenceConfigDlg : public QgsEditorConfigWidget, p
|
||||
void relationChanged( int idx );
|
||||
void mAddFilterButton_clicked();
|
||||
void mRemoveFilterButton_clicked();
|
||||
|
||||
/**
|
||||
* Opens an expression dialog and sets its value as filter expression for the relation reference.
|
||||
*/
|
||||
void mEditExpression_clicked();
|
||||
};
|
||||
|
||||
#endif // QGSRELATIONREFERENCECONFIGDLGBASE_H
|
||||
|
@ -217,6 +217,7 @@ void QgsRelationReferenceSearchWidgetWrapper::initWidget( QWidget *editor )
|
||||
{
|
||||
mWidget->setFilterFields( config( QStringLiteral( "FilterFields" ) ).toStringList() );
|
||||
mWidget->setChainFilters( config( QStringLiteral( "ChainFilters" ) ).toBool() );
|
||||
mWidget->setFilterExpression( config( QStringLiteral( "FilterExpression" ) ).toString() );
|
||||
}
|
||||
|
||||
QgsRelation relation = QgsProject::instance()->relationManager()->relation( config( QStringLiteral( "Relation" ) ).toString() );
|
||||
|
@ -189,6 +189,7 @@ void QgsRelationReferenceWidget::setRelation( const QgsRelation &relation, bool
|
||||
mRelation = relation;
|
||||
mReferencingLayer = relation.referencingLayer();
|
||||
mReferencedLayer = relation.referencedLayer();
|
||||
|
||||
const QList<QgsRelation::FieldPair> fieldPairs = relation.fieldPairs();
|
||||
for ( const QgsRelation::FieldPair &fieldPair : fieldPairs )
|
||||
{
|
||||
@ -199,6 +200,7 @@ void QgsRelationReferenceWidget::setRelation( const QgsRelation &relation, bool
|
||||
mComboBox->setAllowNull( mAllowNull );
|
||||
mComboBox->setSourceLayer( mReferencedLayer );
|
||||
mComboBox->setIdentifierFields( mReferencedFields );
|
||||
mComboBox->setFilterExpression( mFilterExpression );
|
||||
}
|
||||
mAttributeEditorFrame->setObjectName( QStringLiteral( "referencing/" ) + relation.name() );
|
||||
|
||||
@ -487,6 +489,11 @@ void QgsRelationReferenceWidget::setChainFilters( bool chainFilters )
|
||||
mChainFilters = chainFilters;
|
||||
}
|
||||
|
||||
void QgsRelationReferenceWidget::setFilterExpression( const QString &expression )
|
||||
{
|
||||
mFilterExpression = expression;
|
||||
}
|
||||
|
||||
void QgsRelationReferenceWidget::showEvent( QShowEvent *e )
|
||||
{
|
||||
Q_UNUSED( e )
|
||||
@ -542,7 +549,9 @@ void QgsRelationReferenceWidget::init()
|
||||
QVariant nullValue = QgsApplication::nullRepresentation();
|
||||
|
||||
QgsFeature ft;
|
||||
QgsFeatureIterator fit = mReferencedLayer->getFeatures();
|
||||
QgsFeatureIterator fit = mFilterExpression.isEmpty()
|
||||
? mReferencedLayer->getFeatures()
|
||||
: mReferencedLayer->getFeatures( mFilterExpression );
|
||||
while ( fit.nextFeature( ft ) )
|
||||
{
|
||||
const int count = std::min( mFilterComboBoxes.count(), mFilterFields.count() );
|
||||
@ -574,6 +583,9 @@ void QgsRelationReferenceWidget::init()
|
||||
mComboBox->setAllowNull( mAllowNull );
|
||||
mComboBox->setIdentifierFields( mReferencedFields );
|
||||
|
||||
if ( ! mFilterExpression.isEmpty() )
|
||||
mComboBox->setFilterExpression( mFilterExpression );
|
||||
|
||||
QVariant nullValue = QgsApplication::nullRepresentation();
|
||||
|
||||
if ( mChainFilters && mFeature.isValid() )
|
||||
@ -851,7 +863,7 @@ void QgsRelationReferenceWidget::filterChanged()
|
||||
|
||||
QgsFeature f;
|
||||
QgsFeatureIds featureIds;
|
||||
QString filterExpression;
|
||||
QString filterExpression = mFilterExpression;
|
||||
|
||||
// comboboxes have to be disabled before building filters
|
||||
if ( mChainFilters )
|
||||
@ -907,9 +919,16 @@ void QgsRelationReferenceWidget::filterChanged()
|
||||
{
|
||||
QMap<QString, QString> filtersAttrs = filters;
|
||||
filtersAttrs[fieldName] = QgsExpression::createFieldEqualityExpression( fieldName, txt );
|
||||
QString expression = filtersAttrs.values().join( QStringLiteral( " AND " ) );
|
||||
|
||||
QgsAttributeList subset = attrs;
|
||||
|
||||
QString expression = filterExpression;
|
||||
if ( ! filterExpression.isEmpty() && ! filtersAttrs.values().isEmpty() )
|
||||
expression += QStringLiteral( " AND " );
|
||||
|
||||
expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral( " ( " );
|
||||
expression += filtersAttrs.values().join( QStringLiteral( " AND " ) );
|
||||
expression += filtersAttrs.isEmpty() ? QString() : QStringLiteral( " ) " );
|
||||
|
||||
subset << mReferencedLayer->fields().lookupField( fieldName );
|
||||
|
||||
QgsFeatureIterator it( mReferencedLayer->getFeatures( QgsFeatureRequest().setFilterExpression( expression ).setSubsetOfAttributes( subset ) ) );
|
||||
@ -938,7 +957,14 @@ void QgsRelationReferenceWidget::filterChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
filterExpression = filters.values().join( QStringLiteral( " AND " ) );
|
||||
|
||||
if ( ! filterExpression.isEmpty() && ! filters.values().isEmpty() )
|
||||
filterExpression += QStringLiteral( " AND " );
|
||||
|
||||
filterExpression += filters.isEmpty() ? QString() : QStringLiteral( " ( " );
|
||||
filterExpression += filters.values().join( QStringLiteral( " AND " ) );
|
||||
filterExpression += filters.isEmpty() ? QString() : QStringLiteral( " ) " );
|
||||
|
||||
mComboBox->setFilterExpression( filterExpression );
|
||||
}
|
||||
|
||||
|
@ -157,6 +157,17 @@ class GUI_EXPORT QgsRelationReferenceWidget : public QWidget
|
||||
*/
|
||||
void setChainFilters( bool chainFilters );
|
||||
|
||||
/**
|
||||
* Returns the currently set filter expression.
|
||||
*/
|
||||
QString filterExpression() const { return mFilterExpression; };
|
||||
|
||||
/**
|
||||
* If not empty, will be used as filter expression.
|
||||
* Only if this evaluates to TRUE, the value will be shown.
|
||||
*/
|
||||
void setFilterExpression( const QString &filterExpression );
|
||||
|
||||
/**
|
||||
* Returns the related feature (from the referenced layer)
|
||||
* if no feature is related, it returns an invalid feature
|
||||
@ -313,6 +324,7 @@ class GUI_EXPORT QgsRelationReferenceWidget : public QWidget
|
||||
QgsVectorLayer *mReferencingLayer = nullptr;
|
||||
QgsFeatureListComboBox *mComboBox = nullptr;
|
||||
QList<QComboBox *> mFilterComboBoxes;
|
||||
QString mFilterExpression;
|
||||
QWidget *mWindowWidget = nullptr;
|
||||
bool mShown = false;
|
||||
QgsRelation mRelation;
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "qgsproject.h"
|
||||
#include "qgsrelationmanager.h"
|
||||
#include "qgsrelationreferencewidget.h"
|
||||
#include "qgsattributeform.h"
|
||||
|
||||
QgsRelationReferenceWidgetWrapper::QgsRelationReferenceWidgetWrapper( QgsVectorLayer *vl, int fieldIdx, QWidget *editor, QgsMapCanvas *canvas, QgsMessageBar *messageBar, QWidget *parent )
|
||||
: QgsEditorWidgetWrapper( vl, fieldIdx, editor, parent )
|
||||
@ -62,6 +63,7 @@ void QgsRelationReferenceWidgetWrapper::initWidget( QWidget *editor )
|
||||
{
|
||||
mWidget->setFilterFields( config( QStringLiteral( "FilterFields" ) ).toStringList() );
|
||||
mWidget->setChainFilters( config( QStringLiteral( "ChainFilters" ) ).toBool() );
|
||||
mWidget->setFilterExpression( config( QStringLiteral( "FilterExpression" ) ).toString() );
|
||||
}
|
||||
mWidget->setAllowAddFeatures( config( QStringLiteral( "AllowAddFeatures" ), false ).toBool() );
|
||||
|
||||
|
@ -73,6 +73,8 @@ class GUI_EXPORT QgsRelationReferenceWidgetWrapper : public QgsEditorWidgetWrapp
|
||||
private:
|
||||
void updateValues( const QVariant &val, const QVariantList &additionalValues = QVariantList() ) override;
|
||||
|
||||
QString mExpression;
|
||||
|
||||
QgsRelationReferenceWidget *mWidget = nullptr;
|
||||
QgsMapCanvas *mCanvas = nullptr;
|
||||
QgsMessageBar *mMessageBar = nullptr;
|
||||
|
@ -139,6 +139,7 @@ QWidget *QgsValueRelationWidgetWrapper::createWidget( QWidget *parent )
|
||||
{
|
||||
return new QgsFilterLineEdit( parent );
|
||||
}
|
||||
else
|
||||
{
|
||||
return new QComboBox( parent );
|
||||
}
|
||||
|
@ -99,6 +99,33 @@
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="1">
|
||||
<widget class="QToolButton" name="mAddFilterButton">
|
||||
<property name="text">
|
||||
<string>…</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/symbologyAdd.svg</normaloff>:/images/themes/default/symbologyAdd.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" rowspan="4">
|
||||
<widget class="QListWidget" name="mAvailableFieldsList"/>
|
||||
</item>
|
||||
<item row="1" column="2" rowspan="4">
|
||||
<widget class="QListWidget" name="mFilterFieldsList"/>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="mCbxChainFilters">
|
||||
<property name="toolTip">
|
||||
<string>When activated, the filters will restrict the choices of fields to options that are </string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Chain filters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
@ -112,28 +139,6 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QToolButton" name="mAddFilterButton">
|
||||
<property name="text">
|
||||
<string>…</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/images/themes/default/symbologyAdd.svg</normaloff>:/images/themes/default/symbologyAdd.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QToolButton" name="mRemoveFilterButton">
|
||||
<property name="text">
|
||||
<string>…</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/symbologyRemove.svg</normaloff>:/images/themes/default/symbologyRemove.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
@ -147,22 +152,55 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="2" rowspan="4">
|
||||
<widget class="QListWidget" name="mFilterFieldsList"/>
|
||||
</item>
|
||||
<item row="1" column="0" rowspan="4">
|
||||
<widget class="QListWidget" name="mAvailableFieldsList"/>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="mCbxChainFilters">
|
||||
<property name="toolTip">
|
||||
<string>When activated, the filters will restrict the choices of fields to options that are </string>
|
||||
<item row="6" column="0" colspan="3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Filter expression</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="mEditExpression">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mIconExpression.svg</normaloff>:/images/themes/default/mIconExpression.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QToolButton" name="mRemoveFilterButton">
|
||||
<property name="text">
|
||||
<string>Chain filters</string>
|
||||
<string>…</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/symbologyRemove.svg</normaloff>:/images/themes/default/symbologyRemove.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="3">
|
||||
<widget class="QTextEdit" name="mFilterExpression"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -66,6 +66,7 @@ class TestQgsRelationReferenceWidget : public QObject
|
||||
void testAddEntry();
|
||||
void testAddEntryNoGeom();
|
||||
void testDependencies(); // Test relation datasource, id etc. config storage
|
||||
void testSetFilterExpression();
|
||||
|
||||
private:
|
||||
std::unique_ptr<QgsVectorLayer> mLayer1;
|
||||
@ -709,5 +710,30 @@ void TestQgsRelationReferenceWidget::testDependencies()
|
||||
|
||||
}
|
||||
|
||||
void TestQgsRelationReferenceWidget::testSetFilterExpression()
|
||||
{
|
||||
|
||||
// init a relation reference widget
|
||||
QStringList filterFields = { "material", "diameter", "raccord" };
|
||||
|
||||
QWidget parentWidget;
|
||||
QgsRelationReferenceWidget w( &parentWidget );
|
||||
|
||||
QEventLoop loop;
|
||||
connect( qobject_cast<QgsFeatureFilterModel *>( w.mComboBox->model() ), &QgsFeatureFilterModel::filterJobCompleted, &loop, &QEventLoop::quit );
|
||||
|
||||
w.setChainFilters( true );
|
||||
w.setFilterFields( filterFields );
|
||||
w.setRelation( *mRelation, true );
|
||||
w.setFilterExpression( QStringLiteral( " \"material\" = 'iron' " ) );
|
||||
w.init();
|
||||
|
||||
loop.exec();
|
||||
QStringList items = getComboBoxItems( w.mComboBox );
|
||||
QCOMPARE( w.mComboBox->currentText(), QStringLiteral( "NULL" ) );
|
||||
// in case there is no filter, the number of filtered features will be 4
|
||||
QCOMPARE( w.mComboBox->count(), 3 );
|
||||
}
|
||||
|
||||
QGSTEST_MAIN( TestQgsRelationReferenceWidget )
|
||||
#include "testqgsrelationreferencewidget.moc"
|
||||
|
Loading…
x
Reference in New Issue
Block a user