Skip to content

Commit

Permalink
Add filter expressions to features model
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasMizera authored and PeterPetrik committed Nov 17, 2020
1 parent 091b2f6 commit 2e982a0
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 40 deletions.
13 changes: 7 additions & 6 deletions src/quickgui/plugin/editor/qgsquickvaluerelation.qml
Expand Up @@ -62,27 +62,28 @@ Item {
}

Component.onCompleted: {
vrModel.setupValueRelation( config )
currentIndex = vrModel.rowFromAttribute( QgsQuick.FeaturesListModel.KeyColumn, value )
vrModel.setupValueRelation( config )
currentIndex = vrModel.rowFromAttribute( QgsQuick.FeaturesListModel.KeyColumn, value )
}

onPressedChanged: {
if( pressed )
{
customWidget.valueRelationOpened( fieldItem, vrModel )
pressed = false // we close combobox and let custom handler react, it can open combobox via openCombobox()
customWidget.valueRelationOpened( fieldItem, vrModel )
}
}

// Called when user makes selection in the combo box
onItemClicked: {
currentIndex = vrModel.rowFromAttribute( QgsQuick.FeaturesListModel.FeatureId, index )
valueChanged( vrModel.keyFromAttribute( QgsQuick.FeaturesListModel.FeatureId, index ), false )
currentIndex = vrModel.rowFromAttribute( QgsQuick.FeaturesListModel.FeatureId, index )
valueChanged( vrModel.keyFromAttribute( QgsQuick.FeaturesListModel.FeatureId, index ), false )
}

// Called when the same form is used for a different feature
onCurrentEditorValueChanged: {
currentIndex = vrModel.rowFromAttribute( QgsQuick.FeaturesListModel.KeyColumn, value );
currentIndex = vrModel.rowFromAttribute( QgsQuick.FeaturesListModel.KeyColumn, value )
vrModel.currentFeature = featurePair.feature
}
}
}
2 changes: 1 addition & 1 deletion src/quickgui/plugin/qgsquickfeatureform.qml
Expand Up @@ -349,7 +349,7 @@ Item {
model: QgsQuick.SubModel {
id: contentModel
model: form.model
rootIndex: form.model.hasTabs ? form.model.index(currentIndex, 0) : null
rootIndex: form.model.hasTabs ? form.model.index(currentIndex, 0) : undefined
}

delegate: fieldItem
Expand Down
87 changes: 69 additions & 18 deletions src/quickgui/qgsquickfeatureslistmodel.cpp
Expand Up @@ -82,7 +82,7 @@ QVariant QgsQuickFeaturesListModel::data( const QModelIndex &index, int role ) c

QString QgsQuickFeaturesListModel::foundPair( const QgsQuickFeatureLayerPair &pair ) const
{
if ( mFilterExpression.isEmpty() )
if ( mSearchExpression.isEmpty() )
return QString();

QgsFields fields = pair.feature().fields();
Expand All @@ -91,37 +91,68 @@ QString QgsQuickFeaturesListModel::foundPair( const QgsQuickFeatureLayerPair &pa
{
QString attrValue = pair.feature().attribute( field.name() ).toString();

if ( attrValue.toLower().indexOf( mFilterExpression.toLower() ) != -1 )
if ( attrValue.toLower().indexOf( mSearchExpression.toLower() ) != -1 )
return field.name() + ": " + attrValue;
}
return QString();
}

QString QgsQuickFeaturesListModel::buildFilterExpression()
QString QgsQuickFeaturesListModel::buildSearchExpression()
{
if ( mFilterExpression.isEmpty() || !mCurrentLayer )
if ( mSearchExpression.isEmpty() || !mCurrentLayer )
return QString();

const QgsFields fields = mCurrentLayer->fields();
QStringList expressionParts;

bool filterExpressionIsNumeric;
int filterInt = mFilterExpression.toInt( &filterExpressionIsNumeric );
bool searchExpressionIsNumeric;
int filterInt = mSearchExpression.toInt( &searchExpressionIsNumeric );
Q_UNUSED( filterInt ); // we only need to know if expression is numeric, int value is not used

for ( const QgsField &field : fields )
{
if ( field.isNumeric() && filterExpressionIsNumeric )
expressionParts << QStringLiteral( "%1 ~ '%2.*'" ).arg( QgsExpression::quotedColumnRef( field.name() ), mFilterExpression );
if ( field.isNumeric() && searchExpressionIsNumeric )
expressionParts << QStringLiteral( "%1 ~ '%2.*'" ).arg( QgsExpression::quotedColumnRef( field.name() ), mSearchExpression );
else if ( field.type() == QVariant::String )
expressionParts << QStringLiteral( "%1 ILIKE '%%2%'" ).arg( QgsExpression::quotedColumnRef( field.name() ), mFilterExpression );
expressionParts << QStringLiteral( "%1 ILIKE '%%2%'" ).arg( QgsExpression::quotedColumnRef( field.name() ), mSearchExpression );
}

QString expression = QStringLiteral( "(%1)" ).arg( expressionParts.join( QLatin1String( " ) OR ( " ) ) );

return expression;
}

void QgsQuickFeaturesListModel::setupFeatureRequest( QgsFeatureRequest &request )
{
if ( !mFilterExpression.isEmpty() && !mSearchExpression.isEmpty() )
{
request.setFilterExpression( buildSearchExpression() );
request.combineFilterExpression( mFilterExpression );
}
else if ( !mSearchExpression.isEmpty() )
{
request.setFilterExpression( buildSearchExpression() );
}
else if ( !mFilterExpression.isEmpty() )
{
request.setFilterExpression( mFilterExpression );
}

request.setLimit( FEATURES_LIMIT );

// create context for filter expression
if ( !mFilterExpression.isEmpty() && QgsValueRelationFieldFormatter::expressionIsUsable( mFilterExpression, mCurrentFeature ) )
{
QgsExpression exp( mFilterExpression );
QgsExpressionContext filterContext = QgsExpressionContext( QgsExpressionContextUtils::globalProjectLayerScopes( mCurrentLayer ) );

if ( mCurrentFeature.isValid() && QgsValueRelationFieldFormatter::expressionRequiresFormScope( mFilterExpression ) )
filterContext.appendScope( QgsExpressionContextUtils::formScope( mCurrentFeature ) );

request.setExpressionContext( filterContext );
}
}

void QgsQuickFeaturesListModel::loadFeaturesFromLayer( QgsVectorLayer *layer )
{
if ( layer && layer->isValid() )
Expand All @@ -130,12 +161,10 @@ void QgsQuickFeaturesListModel::loadFeaturesFromLayer( QgsVectorLayer *layer )
if ( mCurrentLayer )
{
beginResetModel();

mFeatures.clear();

QgsFeatureRequest req;
if ( !mFilterExpression.isEmpty() )
req.setFilterExpression( buildFilterExpression() );
req.setLimit( FEATURES_LIMIT );
setupFeatureRequest( req );

QgsFeatureIterator it = mCurrentLayer->getFeatures( req );
QgsFeature f;
Expand All @@ -144,6 +173,7 @@ void QgsQuickFeaturesListModel::loadFeaturesFromLayer( QgsVectorLayer *layer )
{
mFeatures << QgsQuickFeatureLayerPair( f, mCurrentLayer );
}

emit featuresCountChanged( featuresCount() );
endResetModel();
}
Expand All @@ -164,6 +194,11 @@ void QgsQuickFeaturesListModel::setupValueRelation( const QVariantMap &config )
setKeyField( fields.field( config.value( QStringLiteral( "Key" ) ).toString() ).name() );
setFeatureTitleField( fields.field( config.value( QStringLiteral( "Value" ) ).toString() ).name() );

// store value relation filter expression
setFilterExpression( config.value( QStringLiteral("FilterExpression") ).toString() );

// config.value( QStringLiteral("AllowMulti") );

loadFeaturesFromLayer( layer );
}

Expand Down Expand Up @@ -191,6 +226,7 @@ void QgsQuickFeaturesListModel::emptyData()
mKeyField.clear();
mFeatureTitleField.clear();
mFilterExpression.clear();
mSearchExpression.clear();
}

QHash<int, QByteArray> QgsQuickFeaturesListModel::roleNames() const
Expand All @@ -212,15 +248,15 @@ int QgsQuickFeaturesListModel::featuresCount() const
return 0;
}

QString QgsQuickFeaturesListModel::filterExpression() const
QString QgsQuickFeaturesListModel::searchExpression() const
{
return mFilterExpression;
return mSearchExpression;
}

void QgsQuickFeaturesListModel::setFilterExpression( const QString &filterExpression )
void QgsQuickFeaturesListModel::setSearchExpression( const QString &searchExpression )
{
mFilterExpression = filterExpression;
emit filterExpressionChanged( mFilterExpression );
mSearchExpression = searchExpression;
emit searchExpressionChanged( mSearchExpression );

loadFeaturesFromLayer();
}
Expand All @@ -235,6 +271,21 @@ void QgsQuickFeaturesListModel::setKeyField( const QString &attribute )
mKeyField = attribute;
}

void QgsQuickFeaturesListModel::setFilterExpression( const QString &filterExpression )
{
mFilterExpression = filterExpression;
}

void QgsQuickFeaturesListModel::setCurrentFeature( QgsFeature feature )
{
if ( mCurrentFeature == feature )
return;

mCurrentFeature = feature;
reloadFeatures();
emit currentFeatureChanged( mCurrentFeature );
}

int QgsQuickFeaturesListModel::featuresLimit() const
{
return FEATURES_LIMIT;
Expand Down
57 changes: 42 additions & 15 deletions src/quickgui/qgsquickfeatureslistmodel.h
Expand Up @@ -38,22 +38,28 @@ class QUICK_EXPORT QgsQuickFeaturesListModel : public QAbstractListModel

/**
* Read only property holding true number of features in layer - not only requested features
* Changing filter expression does not result in changing this number
* Changing search expression does not result in changing this number
*/
Q_PROPERTY( int featuresCount READ featuresCount NOTIFY featuresCountChanged )

/**
* Filter Expression represents filter used when querying for data in current layer.
* String and numerical attributes are compared with filterExpression
* Search expression represents a filter used when querying for data in current layer.
* Changing this property results in reloading features from current layer with new search expression.
*/
Q_PROPERTY( QString filterExpression READ filterExpression WRITE setFilterExpression NOTIFY filterExpressionChanged )
Q_PROPERTY( QString searchExpression READ searchExpression WRITE setSearchExpression NOTIFY searchExpressionChanged )

/**
* Property limiting maximum number of features queried from layer
* Read only property
*/
Q_PROPERTY( int featuresLimit READ featuresLimit NOTIFY featuresLimitChanged )

/**
* Feature that has opened feature form.
* This property needs to be set before opening feature form to be able to evaulate filter expressions that contain form scope.
*/
Q_PROPERTY( QgsFeature currentFeature WRITE setCurrentFeature NOTIFY currentFeatureChanged)

public:

//! Roles for FeaturesListModel
Expand All @@ -64,7 +70,7 @@ class QUICK_EXPORT QgsQuickFeaturesListModel : public QAbstractListModel
Feature,
Description, //! secondary text in list view
KeyColumn, //! key in value relation
FoundPair //! pair of attribute and its value by which the feature was found, empty if mFilterExpression is empty
FoundPair //! pair of attribute and its value by which the feature was found, empty if search expression is empty
};
Q_ENUM( modelRoles );

Expand Down Expand Up @@ -118,14 +124,14 @@ class QUICK_EXPORT QgsQuickFeaturesListModel : public QAbstractListModel
int featuresLimit() const;
//! Returns number of features in layer, not number of loaded features
int featuresCount() const;
//! Returns filter expression, empty string represents no filter
QString filterExpression() const;
//! Returns search expression
QString searchExpression() const;

/**
* \brief setFilterExpression Sets filter expression, upon setting also reloads features from current layer with new filter
* \param filterExpression QString to set, empty string represents no filter
* \brief setSearchExpression Sets search expression, upon setting also reloads features from current layer with new expression
* \param searchExpression QString to set, empty string represents no filter
*/
void setFilterExpression( const QString &filterExpression );
void setSearchExpression( const QString &searchExpression );

/**
* \brief setFeatureTitleField Sets name of attribute that will be used for FeatureTitle and Qt::DisplayRole
Expand All @@ -136,6 +142,15 @@ class QUICK_EXPORT QgsQuickFeaturesListModel : public QAbstractListModel
//! Sets name of attribute used as "key" in value relation
void setKeyField( const QString &attribute );

/**
* \brief setFilterExpression Sets filter expression for current layer that will be used when querying for data
* \param filterExpression to be set
*/
void setFilterExpression( const QString &filterExpression );

//! Sets current feature property
void setCurrentFeature( QgsFeature feature );

signals:

/**
Expand All @@ -147,11 +162,17 @@ class QUICK_EXPORT QgsQuickFeaturesListModel : public QAbstractListModel
//! Signal emitted when maximum number of features that can be loaded changes
void featuresLimitChanged( int featuresLimit );

//! Signal emitted when filter expression has changed
void filterExpressionChanged( QString filterExpression );
//! Signal emitted when search expression has changed
void searchExpressionChanged( QString searchExpression );

//! Signal emitted when current feature has changed
void currentFeatureChanged( QgsFeature feature );

private:

//! Sets maximum limit and filter expression for request.
void setupFeatureRequest( QgsFeatureRequest &request );

//! Reloads features from layer, if layer is not provided, uses current layer, If layer is provided, saves it as current.
void loadFeaturesFromLayer( QgsVectorLayer *layer = nullptr );

Expand All @@ -161,10 +182,10 @@ class QUICK_EXPORT QgsQuickFeaturesListModel : public QAbstractListModel
//! Builds feature title in list
QVariant featureTitle( const QgsQuickFeatureLayerPair &featurePair ) const;

//! Builds filter qgis expression from mFilterExpression
QString buildFilterExpression();
//! Builds qgis filter expression from search expression
QString buildSearchExpression();

//! Returns found attribute and its value from mFilterExpression
//! Returns found attribute and its value from search expression
QString foundPair( const QgsQuickFeatureLayerPair &feat ) const;

/**
Expand All @@ -178,11 +199,17 @@ class QUICK_EXPORT QgsQuickFeaturesListModel : public QAbstractListModel
const int FEATURES_LIMIT = 10000;

//! Search string, change of string results in reloading features from mCurrentLayer
QString mSearchExpression;

//! Contains filter expression of value relation field
QString mFilterExpression;

//! Pointer to layer that is currently loaded
QgsVectorLayer *mCurrentLayer = nullptr;

//! Pointer to a feature that has currently opened feature form, if null - feature form is not opened
QgsFeature mCurrentFeature;

//! Field that is used as a "key" in value relation
QString mKeyField;

Expand Down

0 comments on commit 2e982a0

Please sign in to comment.