QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgsfeaturefiltermodel.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsfeaturefiltermodel.cpp - QgsFeatureFilterModel
3 
4  ---------------------
5  begin : 10.3.2017
6  copyright : (C) 2017 by Matthias Kuhn
7  email : [email protected]
8  ***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 #include "qgsfeaturefiltermodel.h"
18 
19 #include "qgsvectorlayer.h"
20 #include "qgsconditionalstyle.h"
21 #include "qgsapplication.h"
22 #include "qgssettings.h"
23 
25  : QAbstractItemModel( parent )
26 {
27  mReloadTimer.setInterval( 100 );
28  mReloadTimer.setSingleShot( true );
29  connect( &mReloadTimer, &QTimer::timeout, this, &QgsFeatureFilterModel::scheduledReload );
30  setExtraIdentifierValueUnguarded( QVariant() );
31 }
32 
34 {
35  if ( mGatherer )
36  connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, mGatherer, &QgsFieldExpressionValuesGatherer::deleteLater );
37 }
38 
40 {
41  return mSourceLayer;
42 }
43 
45 {
46  if ( mSourceLayer == sourceLayer )
47  return;
48 
49  mSourceLayer = sourceLayer;
50  mExpressionContext = sourceLayer->createExpressionContext();
51  reload();
52  emit sourceLayerChanged();
53 }
54 
56 {
57  return mDisplayExpression.expression();
58 }
59 
61 {
62  if ( mDisplayExpression.expression() == displayExpression )
63  return;
64 
65  mDisplayExpression = QgsExpression( displayExpression );
66  reload();
68 }
69 
71 {
72  return mFilterValue;
73 }
74 
76 {
77  if ( mFilterValue == filterValue )
78  return;
79 
80  mFilterValue = filterValue;
81  reload();
82  emit filterValueChanged();
83 }
84 
86 {
87  return mFilterExpression;
88 }
89 
91 {
92  if ( mFilterExpression == filterExpression )
93  return;
94 
95  mFilterExpression = filterExpression;
96  reload();
98 }
99 
101 {
102  return mGatherer;
103 }
104 
105 QModelIndex QgsFeatureFilterModel::index( int row, int column, const QModelIndex &parent ) const
106 {
107  Q_UNUSED( parent )
108  return createIndex( row, column, nullptr );
109 }
110 
111 QModelIndex QgsFeatureFilterModel::parent( const QModelIndex &child ) const
112 {
113  Q_UNUSED( child )
114  return QModelIndex();
115 }
116 
117 int QgsFeatureFilterModel::rowCount( const QModelIndex &parent ) const
118 {
119  Q_UNUSED( parent )
120 
121  return mEntries.size();
122 }
123 
124 int QgsFeatureFilterModel::columnCount( const QModelIndex &parent ) const
125 {
126  Q_UNUSED( parent )
127  return 1;
128 }
129 
130 QVariant QgsFeatureFilterModel::data( const QModelIndex &index, int role ) const
131 {
132  if ( !index.isValid() )
133  return QVariant();
134 
135  switch ( role )
136  {
137  case Qt::DisplayRole:
138  case Qt::EditRole:
139  case ValueRole:
140  return mEntries.value( index.row() ).value;
141 
142  case IdentifierValueRole:
143  return mEntries.value( index.row() ).identifierValue;
144 
145  case Qt::BackgroundColorRole:
146  case Qt::TextColorRole:
147  case Qt::DecorationRole:
148  case Qt::FontRole:
149  {
150  if ( mEntries.value( index.row() ).identifierValue.isNull() )
151  {
152  // Representation for NULL value
153  if ( role == Qt::TextColorRole )
154  {
155  return QBrush( QColor( Qt::gray ) );
156  }
157  else if ( role == Qt::FontRole )
158  {
159  QFont font = QFont();
160  if ( index.row() == mExtraIdentifierValueIndex )
161  font.setBold( true );
162 
163  if ( mEntries.value( index.row() ).identifierValue.isNull() )
164  {
165  font.setItalic( true );
166  }
167  return font;
168  }
169  }
170  else
171  {
172  // Respect conditional style
173  const QgsConditionalStyle style = featureStyle( mEntries.value( index.row() ).feature );
174 
175  if ( style.isValid() )
176  {
177  if ( role == Qt::BackgroundColorRole && style.validBackgroundColor() )
178  return style.backgroundColor();
179  if ( role == Qt::TextColorRole && style.validTextColor() )
180  return style.textColor();
181  if ( role == Qt::DecorationRole )
182  return style.icon();
183  if ( role == Qt::FontRole )
184  return style.font();
185  }
186  }
187  break;
188  }
189  }
190 
191  return QVariant();
192 }
193 
194 void QgsFeatureFilterModel::updateCompleter()
195 {
196  emit beginUpdate();
197  QVector<Entry> entries = mGatherer->entries();
198 
199  if ( mExtraIdentifierValueIndex == -1 )
200  setExtraIdentifierValueUnguarded( QVariant() );
201 
202  // Only reloading the current entry?
203  if ( mGatherer->data().toBool() )
204  {
205  if ( !entries.isEmpty() )
206  {
207  mEntries.replace( mExtraIdentifierValueIndex, entries.at( 0 ) );
208  emit dataChanged( index( mExtraIdentifierValueIndex, 0, QModelIndex() ), index( mExtraIdentifierValueIndex, 0, QModelIndex() ) );
209  mShouldReloadCurrentFeature = false;
210  setExtraValueDoesNotExist( false );
211  }
212  else
213  {
214  setExtraValueDoesNotExist( true );
215  }
216 
217  mShouldReloadCurrentFeature = false;
218 
219  if ( mFilterValue.isEmpty() )
220  reload();
221  }
222  else
223  {
224  // We got strings for a filter selection
225  std::sort( entries.begin(), entries.end(), []( const Entry & a, const Entry & b ) { return a.value.localeAwareCompare( b.value ) < 0; } );
226 
227  if ( mAllowNull )
228  entries.prepend( Entry( QVariant( QVariant::Int ), QgsApplication::nullRepresentation(), QgsFeature() ) );
229 
230  const int newEntriesSize = entries.size();
231 
232  // Find the index of the extra entry in the new list
233  int currentEntryInNewList = -1;
234  if ( mExtraIdentifierValueIndex != -1 )
235  {
236  for ( int i = 0; i < newEntriesSize; ++i )
237  {
238  if ( entries.at( i ).identifierValue == mExtraIdentifierValue )
239  {
240  currentEntryInNewList = i;
241  mEntries.replace( mExtraIdentifierValueIndex, entries.at( i ) );
242  emit dataChanged( index( mExtraIdentifierValueIndex, 0, QModelIndex() ), index( mExtraIdentifierValueIndex, 0, QModelIndex() ) );
243  setExtraValueDoesNotExist( false );
244  break;
245  }
246  }
247  }
248  else
249  {
250  Q_ASSERT_X( false, "QgsFeatureFilterModel::updateCompleter", "No extra identifier value generated. Should not get here." );
251  }
252 
253  int firstRow = 0;
254 
255  // Move the extra entry to the first position
256  if ( mExtraIdentifierValueIndex != -1 )
257  {
258  if ( mExtraIdentifierValueIndex != 0 )
259  {
260  beginMoveRows( QModelIndex(), mExtraIdentifierValueIndex, mExtraIdentifierValueIndex, QModelIndex(), 0 );
261  mEntries.move( mExtraIdentifierValueIndex, 0 );
262  endMoveRows();
263  }
264  firstRow = 1;
265  }
266 
267  // Remove all entries (except for extra entry if existent)
268  beginRemoveRows( QModelIndex(), firstRow, mEntries.size() - firstRow );
269  mEntries.remove( firstRow, mEntries.size() - firstRow );
270  endRemoveRows();
271 
272  if ( currentEntryInNewList == -1 )
273  {
274  beginInsertRows( QModelIndex(), 1, entries.size() + 1 );
275  mEntries += entries;
276  endInsertRows();
277  setExtraIdentifierValueIndex( 0 );
278  }
279  else
280  {
281  if ( currentEntryInNewList != 0 )
282  {
283  beginInsertRows( QModelIndex(), 0, currentEntryInNewList - 1 );
284  mEntries = entries.mid( 0, currentEntryInNewList ) + mEntries;
285  endInsertRows();
286  }
287  else
288  {
289  mEntries.replace( 0, entries.at( 0 ) );
290  }
291 
292  emit dataChanged( index( currentEntryInNewList, 0, QModelIndex() ), index( currentEntryInNewList, 0, QModelIndex() ) );
293 
294  beginInsertRows( QModelIndex(), currentEntryInNewList + 1, newEntriesSize - currentEntryInNewList - 1 );
295  mEntries += entries.mid( currentEntryInNewList + 1 );
296  endInsertRows();
297  setExtraIdentifierValueIndex( currentEntryInNewList );
298  }
299 
300  emit filterJobCompleted();
301  }
302  emit endUpdate();
303 }
304 
305 void QgsFeatureFilterModel::gathererThreadFinished()
306 {
307  delete mGatherer;
308  mGatherer = nullptr;
309  emit isLoadingChanged();
310 }
311 
312 void QgsFeatureFilterModel::scheduledReload()
313 {
314  if ( !mSourceLayer )
315  return;
316 
317  bool wasLoading = false;
318 
319  if ( mGatherer )
320  {
321  // Send the gatherer thread to the graveyard:
322  // forget about it, tell it to stop and delete when finished
323  disconnect( mGatherer, &QgsFieldExpressionValuesGatherer::collectedValues, this, &QgsFeatureFilterModel::updateCompleter );
324  disconnect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, this, &QgsFeatureFilterModel::gathererThreadFinished );
325  connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, mGatherer, &QgsFieldExpressionValuesGatherer::deleteLater );
326  mGatherer->stop();
327  wasLoading = true;
328  }
329 
330  QgsFeatureRequest request;
331 
332  if ( mShouldReloadCurrentFeature )
333  {
334  request.setFilterExpression( QStringLiteral( "%1 = %2" ).arg( QgsExpression::quotedColumnRef( mIdentifierField ), QgsExpression::quotedValue( mExtraIdentifierValue ) ) );
335  }
336  else
337  {
338  QString filterClause;
339 
340  if ( mFilterValue.isEmpty() && !mFilterExpression.isEmpty() )
341  filterClause = mFilterExpression;
342  else if ( mFilterExpression.isEmpty() && !mFilterValue.isEmpty() )
343  filterClause = QStringLiteral( "(%1) ILIKE '%%2%'" ).arg( mDisplayExpression, mFilterValue );
344  else if ( !mFilterExpression.isEmpty() && !mFilterValue.isEmpty() )
345  filterClause = QStringLiteral( "(%1) AND ((%2) ILIKE '%%3%')" ).arg( mFilterExpression, mDisplayExpression, mFilterValue );
346 
347  if ( !filterClause.isEmpty() )
348  request.setFilterExpression( filterClause );
349  }
350  QSet<QString> attributes;
351  if ( request.filterExpression() )
352  attributes = request.filterExpression()->referencedColumns();
353  attributes << mIdentifierField;
354  request.setSubsetOfAttributes( attributes, mSourceLayer->fields() );
356 
357  request.setLimit( QgsSettings().value( QStringLiteral( "maxEntriesRelationWidget" ), 100, QgsSettings::Gui ).toInt() );
358 
359  mGatherer = new QgsFieldExpressionValuesGatherer( mSourceLayer, mDisplayExpression, mIdentifierField, request );
360  mGatherer->setData( mShouldReloadCurrentFeature );
361 
362  connect( mGatherer, &QgsFieldExpressionValuesGatherer::collectedValues, this, &QgsFeatureFilterModel::updateCompleter );
363  connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, this, &QgsFeatureFilterModel::gathererThreadFinished );
364 
365  mGatherer->start();
366  if ( !wasLoading )
367  emit isLoadingChanged();
368 }
369 
370 QSet<QString> QgsFeatureFilterModel::requestedAttributes() const
371 {
372  QSet<QString> requestedAttrs;
373 
374  const auto rowStyles = mSourceLayer->conditionalStyles()->rowStyles();
375 
376  for ( const QgsConditionalStyle &style : rowStyles )
377  {
378  QgsExpression exp( style.rule() );
379  requestedAttrs += exp.referencedColumns();
380  }
381 
382  if ( mDisplayExpression.isField() )
383  {
384  QString fieldName = *mDisplayExpression.referencedColumns().constBegin();
385  const auto constFieldStyles = mSourceLayer->conditionalStyles()->fieldStyles( fieldName );
386  for ( const QgsConditionalStyle &style : constFieldStyles )
387  {
388  QgsExpression exp( style.rule() );
389  requestedAttrs += exp.referencedColumns();
390  }
391  }
392 
393  return requestedAttrs;
394 }
395 
396 void QgsFeatureFilterModel::setExtraIdentifierValueIndex( int index, bool force )
397 {
398  if ( mExtraIdentifierValueIndex == index && !force )
399  return;
400 
401  mExtraIdentifierValueIndex = index;
403 }
404 
405 void QgsFeatureFilterModel::reloadCurrentFeature()
406 {
407  mShouldReloadCurrentFeature = true;
408  mReloadTimer.start();
409 }
410 
411 void QgsFeatureFilterModel::setExtraIdentifierValueUnguarded( const QVariant &extraIdentifierValue )
412 {
413  const QVector<Entry> entries = mEntries;
414 
415  int index = 0;
416  for ( const Entry &entry : entries )
417  {
418  if ( entry.identifierValue == extraIdentifierValue
419  && entry.identifierValue.isNull() == extraIdentifierValue.isNull()
420  && entry.identifierValue.isValid() == extraIdentifierValue.isValid() )
421  {
422  setExtraIdentifierValueIndex( index );
423  break;
424  }
425 
426  index++;
427  }
428 
429  // Value not found in current entries
430  if ( mExtraIdentifierValueIndex != index )
431  {
432  beginInsertRows( QModelIndex(), 0, 0 );
433  if ( extraIdentifierValue.isNull() )
434  mEntries.prepend( Entry( QVariant( QVariant::Int ), QgsApplication::nullRepresentation( ), QgsFeature() ) );
435  else
436  mEntries.prepend( Entry( extraIdentifierValue, QStringLiteral( "(%1)" ).arg( extraIdentifierValue.toString() ), QgsFeature() ) );
437  endInsertRows();
438 
439  setExtraIdentifierValueIndex( 0, true );
440 
441  reloadCurrentFeature();
442  }
443 }
444 
445 QgsConditionalStyle QgsFeatureFilterModel::featureStyle( const QgsFeature &feature ) const
446 {
447  if ( !mSourceLayer )
448  return QgsConditionalStyle();
449 
450  QgsVectorLayer *layer = mSourceLayer;
451  QgsFeatureId fid = feature.id();
452  mExpressionContext.setFeature( feature );
453 
454  auto styles = QgsConditionalStyle::matchingConditionalStyles( layer->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
455 
456  if ( mDisplayExpression.referencedColumns().count() == 1 )
457  {
458  // Style specific for this field
459  QString fieldName = *mDisplayExpression.referencedColumns().constBegin();
460  const auto allStyles = layer->conditionalStyles()->fieldStyles( fieldName );
461  const auto matchingFieldStyles = QgsConditionalStyle::matchingConditionalStyles( allStyles, feature.attribute( fieldName ), mExpressionContext );
462 
463  styles += matchingFieldStyles;
464  }
465 
466  QgsConditionalStyle style;
467  style = QgsConditionalStyle::compressStyles( styles );
468  mEntryStylesMap.insert( fid, style );
469 
470  return style;
471 }
472 
474 {
475  return mAllowNull;
476 }
477 
479 {
480  if ( mAllowNull == allowNull )
481  return;
482 
483  mAllowNull = allowNull;
484  emit allowNullChanged();
485 
486  reload();
487 }
488 
490 {
491  return mExtraValueDoesNotExist;
492 }
493 
494 void QgsFeatureFilterModel::setExtraValueDoesNotExist( bool extraValueDoesNotExist )
495 {
496  if ( mExtraValueDoesNotExist == extraValueDoesNotExist )
497  return;
498 
499  mExtraValueDoesNotExist = extraValueDoesNotExist;
501 }
502 
504 {
505  return mExtraIdentifierValueIndex;
506 }
507 
509 {
510  return mIdentifierField;
511 }
512 
514 {
515  if ( mIdentifierField == identifierField )
516  return;
517 
518  mIdentifierField = identifierField;
519  emit identifierFieldChanged();
520 }
521 
522 void QgsFeatureFilterModel::reload()
523 {
524  mReloadTimer.start();
525 }
526 
528 {
529  return mExtraIdentifierValue;
530 }
531 
533 {
534  if ( qgsVariantEqual( extraIdentifierValue, mExtraIdentifierValue ) && mExtraIdentifierValue.isValid() )
535  return;
536 
537  if ( mIsSettingExtraIdentifierValue )
538  return;
539 
540  mIsSettingExtraIdentifierValue = true;
541 
542  mExtraIdentifierValue = extraIdentifierValue;
543 
544  setExtraIdentifierValueUnguarded( extraIdentifierValue );
545 
546  mIsSettingExtraIdentifierValue = false;
547 
549 }
Class for parsing and evaluation of expressions (formerly called "search strings").
void filterValueChanged()
This value will be used to filter the features available from this model.
QgsFeatureId id
Definition: qgsfeature.h:64
bool allowNull() const
Add a NULL entry to the list.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
void beginUpdate()
Notification that the model is about to be changed because a job was completed.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
int rowCount(const QModelIndex &parent) const override
Used to retrieve the displayExpression of a feature.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void setFilterExpression(const QString &filterExpression)
An additional filter expression to apply, next to the filterValue.
QString filterValue() const
This value will be used to filter the features available from this model.
QList< QgsConditionalStyle > fieldStyles(const QString &fieldName)
Returns the conditional styles set for the field UI properties.
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QVariant extraIdentifierValue() const
Allows specifying one value that does not need to match the filter criteria but will still be availab...
void filterJobCompleted()
Indicates that a filter job has been completed and new data may be available.
bool validBackgroundColor() const
Check if the background color is valid for render.
QPixmap icon() const
The icon set for style generated from the set symbol.
bool extraValueDoesNotExist() const
Flag indicating that the extraIdentifierValue does not exist in the data.
void extraIdentifierValueIndexChanged(int index)
The index at which the extra identifier value is available within the model.
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
friend class QgsFieldExpressionValuesGatherer
QgsConditionalLayerStyles * conditionalStyles() const
Returns the conditional styles that are set for this layer.
QgsExpression * filterExpression() const
Returns the filter expression if set.
int extraIdentifierValueIndex() const
The index at which the extra identifier value is available within the model.
QList< QgsConditionalStyle > rowStyles()
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
void identifierFieldChanged()
The identifier field should be a unique field that can be used to identify individual features...
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Conditional styling for a rule.
QString displayExpression() const
The display expression will be used for.
bool isValid() const
isValid Check if this rule is valid.
static QList< QgsConditionalStyle > matchingConditionalStyles(const QList< QgsConditionalStyle > &styles, const QVariant &value, QgsExpressionContext &context)
Find and return the matching styles for the value and feature.
void setFilterValue(const QString &filterValue)
This value will be used to filter the features available from this model.
QModelIndex index(int row, int column, const QModelIndex &parent) const override
This class wraps a request for features to a vector layer (or directly its vector data provider)...
QColor backgroundColor() const
The background color for style.
static QString nullRepresentation()
This string is used to represent the value NULL throughout QGIS.
static QgsConditionalStyle compressStyles(const QList< QgsConditionalStyle > &styles)
Compress a list of styles into a single style.
void isLoadingChanged()
Indicator if the model is currently performing any feature iteration in the background.
QModelIndex parent(const QModelIndex &child) const override
void allowNullChanged()
Add a NULL entry to the list.
QgsFeatureFilterModel(QObject *parent=nullptr)
Create a new QgsFeatureFilterModel, optionally specifying a parent.
QgsExpressionContext createExpressionContext() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
bool isLoading() const
Indicator if the model is currently performing any feature iteration in the background.
QString filterExpression() const
An additional filter expression to apply, next to the filterValue.
void filterExpressionChanged()
An additional filter expression to apply, next to the filterValue.
void endUpdate()
Notification that the model change is finished.
QString identifierField() const
The identifier field should be a unique field that can be used to identify individual features...
QString expression() const
Returns the original, unmodified expression string.
int columnCount(const QModelIndex &parent) const override
void extraIdentifierValueChanged()
Allows specifying one value that does not need to match the filter criteria but will still be availab...
void setIdentifierField(const QString &identifierField)
The identifier field should be a unique field that can be used to identify individual features...
void setSourceLayer(QgsVectorLayer *sourceLayer)
The source layer from which features will be fetched.
QColor textColor() const
The text color set for style.
void sourceLayerChanged()
The source layer from which features will be fetched.
bool validTextColor() const
Check if the text color is valid for render.
bool isField() const
Checks whether an expression consists only of a single field reference.
void setExtraIdentifierValue(const QVariant &extraIdentifierValue)
Allows specifying one value that does not need to match the filter criteria but will still be availab...
QgsFeatureRequest & setLimit(long limit)
Set the maximum number of features to request.
QVariant data(const QModelIndex &index, int role) const override
void setAllowNull(bool allowNull)
Add a NULL entry to the list.
QFont font() const
The font for the style.
static QString quotedValue(const QVariant &value)
Returns a string representation of a literal value, including appropriate quotations where required...
QgsVectorLayer * sourceLayer() const
The source layer from which features will be fetched.
bool qgsVariantEqual(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether they are equal, NULL values are treated as equal...
Definition: qgis.cpp:289
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Used to retrieve the identifierValue (primary key) of a feature.
void setDisplayExpression(const QString &displayExpression)
The display expression will be used for.
Represents a vector layer which manages a vector based data sets.
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
void extraValueDoesNotExistChanged()
Flag indicating that the extraIdentifierValue does not exist in the data.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
void displayExpressionChanged()
The display expression will be used for.