QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
qgsquickfeatureslistmodel.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsquickfeatureslistmodel.cpp
3  ---------------------------
4  Date : Sep 2020
5  Copyright : (C) 2020 by Tomas Mizera
6  Email : tomas.mizera2 at gmail dot com
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 
18 #include "qgslogger.h"
19 
21  : QAbstractListModel( parent ),
22  mCurrentLayer( nullptr )
23 {
24 }
25 
27 
28 int QgsQuickFeaturesListModel::rowCount( const QModelIndex &parent ) const
29 {
30  // For list models only the root node (an invalid parent) should return the list's size. For all
31  // other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
32  if ( parent.isValid() )
33  return 0;
34 
35  return mFeatures.count();
36 }
37 
38 QVariant QgsQuickFeaturesListModel::featureTitle( const QgsQuickFeatureLayerPair &featurePair ) const
39 {
40  QString title;
41 
42  if ( !mFeatureTitleField.isEmpty() )
43  {
44  title = featurePair.feature().attribute( mFeatureTitleField ).toString();
45  if ( !title.isEmpty() )
46  return title;
47  }
48 
50  context.setFeature( featurePair.feature() );
51  QgsExpression expr( featurePair.layer()->displayExpression() );
52  title = expr.evaluate( &context ).toString();
53 
54  if ( title.isEmpty() )
55  return featurePair.feature().id();
56 
57  return title;
58 }
59 
60 QVariant QgsQuickFeaturesListModel::data( const QModelIndex &index, int role ) const
61 {
62  int row = index.row();
63  if ( row < 0 || row >= mFeatures.count() )
64  return QVariant();
65 
66  if ( !index.isValid() )
67  return QVariant();
68 
69  const QgsQuickFeatureLayerPair pair = mFeatures.at( index.row() );
70 
71  switch ( role )
72  {
73  case FeatureTitle: return featureTitle( pair );
74  case FeatureId: return QVariant( pair.feature().id() );
75  case Feature: return QVariant::fromValue<QgsFeature>( pair.feature() );
76  case Description: return QVariant( QString( "Feature ID %1" ).arg( pair.feature().id() ) );
77  case KeyColumn: return mKeyField.isEmpty() ? QVariant() : pair.feature().attribute( mKeyField );
78  case FoundPair: return foundPair( pair );
79  case Qt::DisplayRole: return featureTitle( pair );
80  }
81 
82  return QVariant();
83 }
84 
85 QString QgsQuickFeaturesListModel::foundPair( const QgsQuickFeatureLayerPair &pair ) const
86 {
87  if ( mSearchExpression.isEmpty() )
88  return QString();
89 
90  QgsFields fields = pair.feature().fields();
91  QStringList words = mSearchExpression.split( ' ', QString::SplitBehavior::SkipEmptyParts );
92  QStringList foundPairs;
93 
94  for ( const QString &word : words )
95  {
96  for ( const QgsField &field : fields )
97  {
98  QString attrValue = pair.feature().attribute( field.name() ).toString();
99 
100  if ( attrValue.toLower().indexOf( word.toLower() ) != -1 )
101  {
102  foundPairs << field.name() + ": " + attrValue;
103 
104  // remove found field from list of fields to not select it more than once
105  fields.remove( fields.lookupField( field.name() ) );
106  }
107  }
108  }
109 
110  return foundPairs.join( ", " );
111 }
112 
113 QString QgsQuickFeaturesListModel::buildSearchExpression()
114 {
115  if ( mSearchExpression.isEmpty() || !mCurrentLayer )
116  return QString();
117 
118  const QgsFields fields = mCurrentLayer->fields();
119  QStringList expressionParts;
120  QStringList wordExpressions;
121 
122  QStringList words = mSearchExpression.split( ' ', QString::SplitBehavior::SkipEmptyParts );
123 
124  for ( const QString &word : words )
125  {
126  bool searchExpressionIsNumeric;
127  int filterInt = word.toInt( &searchExpressionIsNumeric );
128  Q_UNUSED( filterInt ); // we only need to know if expression is numeric, int value is not used
129 
130 
131  for ( const QgsField &field : fields )
132  {
133  if ( field.isNumeric() && searchExpressionIsNumeric )
134  expressionParts << QStringLiteral( "%1 ~ '%2.*'" ).arg( QgsExpression::quotedColumnRef( field.name() ), word );
135  else if ( field.type() == QVariant::String )
136  expressionParts << QStringLiteral( "%1 ILIKE '%%2%'" ).arg( QgsExpression::quotedColumnRef( field.name() ), word );
137  }
138  wordExpressions << QStringLiteral( "(%1)" ).arg( expressionParts.join( QLatin1String( " ) OR ( " ) ) );
139  expressionParts.clear();
140  }
141 
142  QString expression = QStringLiteral( "(%1)" ).arg( wordExpressions.join( QLatin1String( " ) AND ( " ) ) );
143 
144  return expression;
145 }
146 
147 void QgsQuickFeaturesListModel::setupFeatureRequest( QgsFeatureRequest &request )
148 {
149  if ( !mFilterExpression.isEmpty() && !mSearchExpression.isEmpty() )
150  {
151  request.setFilterExpression( buildSearchExpression() );
152  request.combineFilterExpression( mFilterExpression );
153  }
154  else if ( !mSearchExpression.isEmpty() )
155  {
156  request.setFilterExpression( buildSearchExpression() );
157  }
158  else if ( !mFilterExpression.isEmpty() )
159  {
160  request.setFilterExpression( mFilterExpression );
161  }
162 
163  request.setLimit( FEATURES_LIMIT );
164 
165  // create context for filter expression
166  if ( !mFilterExpression.isEmpty() && QgsValueRelationFieldFormatter::expressionIsUsable( mFilterExpression, mCurrentFeature ) )
167  {
168  QgsExpression exp( mFilterExpression );
170 
171  if ( mCurrentFeature.isValid() && QgsValueRelationFieldFormatter::expressionRequiresFormScope( mFilterExpression ) )
172  filterContext.appendScope( QgsExpressionContextUtils::formScope( mCurrentFeature ) );
173 
174  request.setExpressionContext( filterContext );
175  }
176 }
177 
178 void QgsQuickFeaturesListModel::loadFeaturesFromLayer( QgsVectorLayer *layer )
179 {
180  if ( layer && layer->isValid() )
181  mCurrentLayer = layer;
182 
183  if ( mCurrentLayer )
184  {
185  beginResetModel();
186  mFeatures.clear();
187 
188  QgsFeatureRequest req;
189  setupFeatureRequest( req );
190 
191  QgsFeatureIterator it = mCurrentLayer->getFeatures( req );
192  QgsFeature f;
193 
194  while ( it.nextFeature( f ) )
195  {
196  mFeatures << QgsQuickFeatureLayerPair( f, mCurrentLayer );
197  }
198 
200  endResetModel();
201  }
202 }
203 
204 void QgsQuickFeaturesListModel::setupValueRelation( const QVariantMap &config )
205 {
206  beginResetModel();
207  emptyData();
208 
210 
211  if ( layer )
212  {
213  // save key and value field
214  QgsFields fields = layer->fields();
215 
216  setKeyField( fields.field( config.value( QStringLiteral( "Key" ) ).toString() ).name() );
217  setFeatureTitleField( fields.field( config.value( QStringLiteral( "Value" ) ).toString() ).name() );
218 
219  // store value relation filter expression
220  setFilterExpression( config.value( QStringLiteral( "FilterExpression" ) ).toString() );
221 
222  loadFeaturesFromLayer( layer );
223  }
224 
225  endResetModel();
226 }
227 
229 {
230  beginResetModel();
231  emptyData();
232 
233  loadFeaturesFromLayer( layer );
234  endResetModel();
235 }
236 
238 {
239  loadFeaturesFromLayer();
240 }
241 
242 void QgsQuickFeaturesListModel::emptyData()
243 {
244  mFeatures.clear();
245  mCurrentLayer = nullptr;
246  mKeyField.clear();
247  mFeatureTitleField.clear();
248  mFilterExpression.clear();
249  mSearchExpression.clear();
250  mCurrentFeature = QgsFeature();
251 }
252 
253 QHash<int, QByteArray> QgsQuickFeaturesListModel::roleNames() const
254 {
255  QHash<int, QByteArray> roleNames = QAbstractListModel::roleNames();
256  roleNames[FeatureTitle] = QStringLiteral( "FeatureTitle" ).toLatin1();
257  roleNames[FeatureId] = QStringLiteral( "FeatureId" ).toLatin1();
258  roleNames[Feature] = QStringLiteral( "Feature" ).toLatin1();
259  roleNames[Description] = QStringLiteral( "Description" ).toLatin1();
260  roleNames[FoundPair] = QStringLiteral( "FoundPair" ).toLatin1();
261  roleNames[KeyColumn] = QStringLiteral( "KeyColumn" ).toLatin1();
262  return roleNames;
263 }
264 
266 {
267  if ( mCurrentLayer && mCurrentLayer->isValid() )
268  return mCurrentLayer->featureCount();
269  return 0;
270 }
271 
273 {
274  return mSearchExpression;
275 }
276 
277 void QgsQuickFeaturesListModel::setSearchExpression( const QString &searchExpression )
278 {
279  mSearchExpression = searchExpression;
280  emit searchExpressionChanged( mSearchExpression );
281 
282  loadFeaturesFromLayer();
283 }
284 
285 void QgsQuickFeaturesListModel::setFeatureTitleField( const QString &attribute )
286 {
287  mFeatureTitleField = attribute;
288 }
289 
290 void QgsQuickFeaturesListModel::setKeyField( const QString &attribute )
291 {
292  mKeyField = attribute;
293 }
294 
295 void QgsQuickFeaturesListModel::setFilterExpression( const QString &filterExpression )
296 {
297  mFilterExpression = filterExpression;
298 }
299 
301 {
302  if ( mCurrentFeature == feature )
303  return;
304 
305  mCurrentFeature = feature;
306  reloadFeatures();
307  emit currentFeatureChanged( mCurrentFeature );
308 }
309 
311 {
312  return mCurrentFeature;
313 }
314 
316 {
317  return FEATURES_LIMIT;
318 }
319 
320 int QgsQuickFeaturesListModel::rowFromAttribute( const int role, const QVariant &value ) const
321 {
322  for ( int i = 0; i < mFeatures.count(); ++i )
323  {
324  QVariant d = data( index( i, 0 ), role );
325  if ( d == value )
326  {
327  return i;
328  }
329  }
330  return -1;
331 }
332 
333 QVariant QgsQuickFeaturesListModel::attributeFromValue( const int role, const QVariant &value, const int requestedRole ) const
334 {
335  for ( int i = 0; i < mFeatures.count(); ++i )
336  {
337  QVariant d = data( index( i, 0 ), role );
338  if ( d == value )
339  {
340  QVariant key = data( index( i, 0 ), requestedRole );
341  return key;
342  }
343  }
344  return QVariant();
345 }
346 
347 QVariant QgsQuickFeaturesListModel::convertMultivalueFormat( const QVariant &multivalue, const int role )
348 {
349  QStringList list = QgsValueRelationFieldFormatter::valueToStringList( multivalue );
350  QList<QVariant> retList;
351 
352  for ( const QString &i : list )
353  {
354  QVariant var = attributeFromValue( KeyColumn, i, role );
355  if ( !var.isNull() )
356  retList.append( var );
357  }
358 
359  return retList;
360 }
361 
363 {
364  for ( const QgsQuickFeatureLayerPair &i : mFeatures )
365  {
366  if ( i.feature().id() == featureId )
367  return i;
368  }
369  return QgsQuickFeatureLayerPair();
370 }
static QgsExpressionContextScope * formScope(const QgsFeature &formFeature=QgsFeature(), const QString &formMode=QString())
Creates a new scope which contains functions and variables from the current attribute form/table form...
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & combineFilterExpression(const QString &expression)
Modifies the existing filter expression to add an additional expression filter.
QgsFeatureRequest & setLimit(long limit)
Set the maximum number of features to request.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
QgsFeatureRequest & setExpressionContext(const QgsExpressionContext &context)
Sets the expression context used to evaluate filter expressions.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:56
QgsFields fields
Definition: qgsfeature.h:66
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:190
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:287
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString name
Definition: qgsfield.h:60
Q_GADGET bool isNumeric
Definition: qgsfield.h:54
QVariant::Type type
Definition: qgsfield.h:58
Container of fields for a vector layer.
Definition: qgsfields.h:45
QgsField field(int fieldIdx) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:168
bool isValid
Definition: qgsmaplayer.h:93
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:501
Pair of QgsFeature and QgsVectorLayer.
QgsFeature feature
Feature that belongs to layer.
Q_GADGET QgsVectorLayer * layer
Vector layer to which the feature belongs.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
QString searchExpression
Search expression represents a filter used when querying for data in current layer.
QgsFeature currentFeature
Feature that has opened feature form.
~QgsQuickFeaturesListModel() override
Q_INVOKABLE void setupValueRelation(const QVariantMap &config)
setupValueRelation populates model with value relation data from config
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
QHash< int, QByteArray > roleNames() const override
QgsQuickFeaturesListModel(QObject *parent=nullptr)
Create features list model.
void currentFeatureChanged(QgsFeature feature)
Signal emitted when current feature has changed.
int featuresCount
Read only property holding true number of features in layer - not only requested features Changing se...
Q_INVOKABLE int rowFromAttribute(const int role, const QVariant &value) const
rowFromAttribute finds feature with requested role and value, returns its row
void featuresCountChanged(int featuresCount)
featuresCountChanged Signal emitted when features are reloaded or layer is changed
void setKeyField(const QString &attribute)
Sets name of attribute used as "key" in value relation.
Q_INVOKABLE void populateFromLayer(QgsVectorLayer *layer)
populateFromLayer populates model with features from layer
Q_INVOKABLE QVariant attributeFromValue(const int role, const QVariant &value, const int requestedRole) const
attributeFromValue finds feature with role and value, returns value for requested role
@ KeyColumn
secondary text in list view
@ FoundPair
key in value relation
Q_INVOKABLE QgsQuickFeatureLayerPair featureLayerPair(const int &featureId)
Function to get QgsQuickFeatureLayerPair by feature id.
Q_INVOKABLE void reloadFeatures()
reloadFeatures reloads features from current layer
void setFeatureTitleField(const QString &attribute)
setFeatureTitleField Sets name of attribute that will be used for FeatureTitle and Qt::DisplayRole
void setSearchExpression(const QString &searchExpression)
setSearchExpression Sets search expression, upon setting also reloads features from current layer wit...
void setFilterExpression(const QString &filterExpression)
setFilterExpression Sets filter expression for current layer that will be used when querying for data
void searchExpressionChanged(QString searchExpression)
Signal emitted when search expression has changed.
int featuresLimit
Property limiting maximum number of features queried from layer Read only property.
void setCurrentFeature(QgsFeature feature)
Sets current feature property.
Q_INVOKABLE QVariant convertMultivalueFormat(const QVariant &multivalue, const int requestedRole=Qt::DisplayRole)
convertMultivalueFormat converts postgres string like string to an array of variants with requested r...
static QgsVectorLayer * resolveLayer(const QVariantMap &config, const QgsProject *project)
Returns the (possibly NULL) layer from the widget's config and project.
static bool expressionRequiresFormScope(const QString &expression)
Check if the expression requires a form scope (i.e.
static QStringList valueToStringList(const QVariant &value)
Utility to convert a list or a string representation of an (hstore style: {1,2...}) list in value to ...
static bool expressionIsUsable(const QString &expression, const QgsFeature &feature, const QgsFeature &parentFeature=QgsFeature())
Check whether the feature has all values required by the expression, optionally checks for parentFeat...
Represents a vector layer which manages a vector based data sets.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QString displayExpression
long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
const QgsField & field
Definition: qgsfield.h:472