QGIS API Documentation  3.2.0-Bonn (bc43194)
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 "qgsconditionalstyle.h"
20 
22  : QAbstractItemModel( parent )
23 {
24  mReloadTimer.setInterval( 100 );
25  mReloadTimer.setSingleShot( true );
26  connect( &mReloadTimer, &QTimer::timeout, this, &QgsFeatureFilterModel::scheduledReload );
27  setExtraIdentifierValueUnguarded( QVariant() );
28 }
29 
31 {
32  if ( mGatherer )
33  connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, mGatherer, &QgsFieldExpressionValuesGatherer::deleteLater );
34 }
35 
37 {
38  return mSourceLayer;
39 }
40 
42 {
43  if ( mSourceLayer == sourceLayer )
44  return;
45 
46  mSourceLayer = sourceLayer;
47  mExpressionContext = sourceLayer->createExpressionContext();
48  reload();
49  emit sourceLayerChanged();
50 }
51 
53 {
54  return mDisplayExpression.expression();
55 }
56 
58 {
59  if ( mDisplayExpression.expression() == displayExpression )
60  return;
61 
62  mDisplayExpression = QgsExpression( displayExpression );
63  reload();
65 }
66 
68 {
69  return mFilterValue;
70 }
71 
73 {
74  if ( mFilterValue == filterValue )
75  return;
76 
77  mFilterValue = filterValue;
78  reload();
79  emit filterValueChanged();
80 }
81 
83 {
84  return mFilterExpression;
85 }
86 
88 {
89  if ( mFilterExpression == filterExpression )
90  return;
91 
92  mFilterExpression = filterExpression;
93  reload();
95 }
96 
98 {
99  return mGatherer;
100 }
101 
102 QModelIndex QgsFeatureFilterModel::index( int row, int column, const QModelIndex &parent ) const
103 {
104  Q_UNUSED( parent )
105  return createIndex( row, column, nullptr );
106 }
107 
108 QModelIndex QgsFeatureFilterModel::parent( const QModelIndex &child ) const
109 {
110  Q_UNUSED( child )
111  return QModelIndex();
112 }
113 
114 int QgsFeatureFilterModel::rowCount( const QModelIndex &parent ) const
115 {
116  Q_UNUSED( parent );
117 
118  return mEntries.size();
119 }
120 
121 int QgsFeatureFilterModel::columnCount( const QModelIndex &parent ) const
122 {
123  Q_UNUSED( parent )
124  return 1;
125 }
126 
127 QVariant QgsFeatureFilterModel::data( const QModelIndex &index, int role ) const
128 {
129  if ( !index.isValid() )
130  return QVariant();
131 
132  switch ( role )
133  {
134  case Qt::DisplayRole:
135  case Qt::EditRole:
136  case ValueRole:
137  return mEntries.value( index.row() ).value;
138 
139  case IdentifierValueRole:
140  return mEntries.value( index.row() ).identifierValue;
141 
142  case Qt::BackgroundColorRole:
143  case Qt::TextColorRole:
144  case Qt::DecorationRole:
145  case Qt::FontRole:
146  {
147  if ( mEntries.value( index.row() ).identifierValue.isNull() )
148  {
149  // Representation for NULL value
150  if ( role == Qt::TextColorRole )
151  {
152  return QBrush( QColor( Qt::gray ) );
153  }
154  else if ( role == Qt::FontRole )
155  {
156  QFont font = QFont();
157  if ( index.row() == mExtraIdentifierValueIndex )
158  font.setBold( true );
159 
160  if ( mEntries.value( index.row() ).identifierValue.isNull() )
161  {
162  font.setItalic( true );
163  }
164  return font;
165  }
166  }
167  else
168  {
169  // Respect conditional style
170  const QgsConditionalStyle style = featureStyle( mEntries.value( index.row() ).feature );
171 
172  if ( style.isValid() )
173  {
174  if ( role == Qt::BackgroundColorRole && style.validBackgroundColor() )
175  return style.backgroundColor();
176  if ( role == Qt::TextColorRole && style.validTextColor() )
177  return style.textColor();
178  if ( role == Qt::DecorationRole )
179  return style.icon();
180  if ( role == Qt::FontRole )
181  return style.font();
182  }
183  }
184  break;
185  }
186  }
187 
188  return QVariant();
189 }
190 
191 void QgsFeatureFilterModel::updateCompleter()
192 {
193  emit beginUpdate();
194  QVector<Entry> entries = mGatherer->entries();
195 
196  if ( mExtraIdentifierValueIndex == -1 )
197  setExtraIdentifierValueUnguarded( QVariant() );
198 
199  // Only reloading the current entry?
200  if ( mGatherer->data().toBool() )
201  {
202  if ( !entries.isEmpty() )
203  {
204  mEntries.replace( mExtraIdentifierValueIndex, entries.at( 0 ) );
205  emit dataChanged( index( mExtraIdentifierValueIndex, 0, QModelIndex() ), index( mExtraIdentifierValueIndex, 0, QModelIndex() ) );
206  mShouldReloadCurrentFeature = false;
207  setExtraValueDoesNotExist( false );
208  }
209  else
210  {
211  setExtraValueDoesNotExist( true );
212  }
213 
214  mShouldReloadCurrentFeature = false;
215 
216  if ( mFilterValue.isEmpty() )
217  reload();
218  }
219  else
220  {
221  // We got strings for a filter selection
222  std::sort( entries.begin(), entries.end(), []( const Entry & a, const Entry & b ) { return a.value.localeAwareCompare( b.value ) < 0; } );
223 
224  if ( mAllowNull )
225  entries.prepend( Entry( QVariant( QVariant::Int ), tr( "NULL" ), QgsFeature() ) );
226 
227  const int newEntriesSize = entries.size();
228 
229  // Find the index of the extra entry in the new list
230  int currentEntryInNewList = -1;
231  if ( mExtraIdentifierValueIndex != -1 )
232  {
233  for ( int i = 0; i < newEntriesSize; ++i )
234  {
235  if ( entries.at( i ).identifierValue == mExtraIdentifierValue )
236  {
237  currentEntryInNewList = i;
238  mEntries.replace( mExtraIdentifierValueIndex, entries.at( i ) );
239  emit dataChanged( index( mExtraIdentifierValueIndex, 0, QModelIndex() ), index( mExtraIdentifierValueIndex, 0, QModelIndex() ) );
240  setExtraValueDoesNotExist( false );
241  break;
242  }
243  }
244  }
245  else
246  {
247  Q_ASSERT_X( false, "QgsFeatureFilterModel::updateCompleter", "No extra identifier value generated. Should not get here." );
248  }
249 
250  int firstRow = 0;
251 
252  // Move the extra entry to the first position
253  if ( mExtraIdentifierValueIndex != -1 )
254  {
255  if ( mExtraIdentifierValueIndex != 0 )
256  {
257  beginMoveRows( QModelIndex(), mExtraIdentifierValueIndex, mExtraIdentifierValueIndex, QModelIndex(), 0 );
258 #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
259  Entry extraEntry = mEntries.takeAt( mExtraIdentifierValueIndex );
260  mEntries.prepend( extraEntry );
261 #else
262  mEntries.move( mExtraIdentifierValueIndex, 0 );
263 #endif
264  endMoveRows();
265  }
266  firstRow = 1;
267  }
268 
269  // Remove all entries (except for extra entry if existent)
270  beginRemoveRows( QModelIndex(), firstRow, mEntries.size() - firstRow );
271  mEntries.remove( firstRow, mEntries.size() - firstRow );
272  endRemoveRows();
273 
274  if ( currentEntryInNewList == -1 )
275  {
276  beginInsertRows( QModelIndex(), 1, entries.size() + 1 );
277  mEntries += entries;
278  endInsertRows();
279  setExtraIdentifierValueIndex( 0 );
280  }
281  else
282  {
283  if ( currentEntryInNewList != 0 )
284  {
285  beginInsertRows( QModelIndex(), 0, currentEntryInNewList - 1 );
286  mEntries = entries.mid( 0, currentEntryInNewList ) + mEntries;
287  endInsertRows();
288  }
289  else
290  {
291  mEntries.replace( 0, entries.at( 0 ) );
292  }
293 
294  emit dataChanged( index( currentEntryInNewList, 0, QModelIndex() ), index( currentEntryInNewList, 0, QModelIndex() ) );
295 
296  beginInsertRows( QModelIndex(), currentEntryInNewList + 1, newEntriesSize - currentEntryInNewList - 1 );
297  mEntries += entries.mid( currentEntryInNewList + 1 );
298  endInsertRows();
299  setExtraIdentifierValueIndex( currentEntryInNewList );
300  }
301 
302  emit filterJobCompleted();
303  }
304  emit endUpdate();
305 }
306 
307 void QgsFeatureFilterModel::gathererThreadFinished()
308 {
309  delete mGatherer;
310  mGatherer = nullptr;
311  emit isLoadingChanged();
312 }
313 
314 void QgsFeatureFilterModel::scheduledReload()
315 {
316  if ( !mSourceLayer )
317  return;
318 
319  bool wasLoading = false;
320 
321  if ( mGatherer )
322  {
323  // Send the gatherer thread to the graveyard:
324  // forget about it, tell it to stop and delete when finished
325  disconnect( mGatherer, &QgsFieldExpressionValuesGatherer::collectedValues, this, &QgsFeatureFilterModel::updateCompleter );
326  disconnect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, this, &QgsFeatureFilterModel::gathererThreadFinished );
327  connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, mGatherer, &QgsFieldExpressionValuesGatherer::deleteLater );
328  mGatherer->stop();
329  wasLoading = true;
330  }
331 
332  QgsFeatureRequest request;
333 
334  if ( mShouldReloadCurrentFeature )
335  {
336  request.setFilterExpression( QStringLiteral( "%1 = %2" ).arg( QgsExpression::quotedColumnRef( mIdentifierField ), QgsExpression::quotedValue( mExtraIdentifierValue ) ) );
337  }
338  else
339  {
340  QString filterClause;
341 
342  if ( mFilterValue.isEmpty() && !mFilterExpression.isEmpty() )
343  filterClause = mFilterExpression;
344  else if ( mFilterExpression.isEmpty() && !mFilterValue.isEmpty() )
345  filterClause = QStringLiteral( "(%1) ILIKE '%%2%'" ).arg( mDisplayExpression, mFilterValue );
346  else if ( !mFilterExpression.isEmpty() && !mFilterValue.isEmpty() )
347  filterClause = QStringLiteral( "(%1) AND ((%2) ILIKE '%%3%')" ).arg( mFilterExpression, mDisplayExpression, mFilterValue );
348 
349  if ( !filterClause.isEmpty() )
350  request.setFilterExpression( filterClause );
351  }
352  QSet<QString> attributes;
353  if ( request.filterExpression() )
354  attributes = request.filterExpression()->referencedColumns();
355  attributes << mIdentifierField;
356  request.setSubsetOfAttributes( attributes, mSourceLayer->fields() );
358 
359  request.setLimit( 100 );
360 
361  mGatherer = new QgsFieldExpressionValuesGatherer( mSourceLayer, mDisplayExpression, mIdentifierField, request );
362  mGatherer->setData( mShouldReloadCurrentFeature );
363 
364  connect( mGatherer, &QgsFieldExpressionValuesGatherer::collectedValues, this, &QgsFeatureFilterModel::updateCompleter );
365  connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, this, &QgsFeatureFilterModel::gathererThreadFinished );
366 
367  mGatherer->start();
368  if ( !wasLoading )
369  emit isLoadingChanged();
370 }
371 
372 QSet<QString> QgsFeatureFilterModel::requestedAttributes() const
373 {
374  QSet<QString> requestedAttrs;
375 
376  const auto rowStyles = mSourceLayer->conditionalStyles()->rowStyles();
377 
378  for ( const QgsConditionalStyle &style : rowStyles )
379  {
380  QgsExpression exp( style.rule() );
381  requestedAttrs += exp.referencedColumns();
382  }
383 
384  if ( mDisplayExpression.isField() )
385  {
386  QString fieldName = *mDisplayExpression.referencedColumns().constBegin();
387  Q_FOREACH ( const QgsConditionalStyle &style, mSourceLayer->conditionalStyles()->fieldStyles( fieldName ) )
388  {
389  QgsExpression exp( style.rule() );
390  requestedAttrs += exp.referencedColumns();
391  }
392  }
393 
394  return requestedAttrs;
395 }
396 
397 void QgsFeatureFilterModel::setExtraIdentifierValueIndex( int index, bool force )
398 {
399  if ( mExtraIdentifierValueIndex == index && !force )
400  return;
401 
402  mExtraIdentifierValueIndex = index;
404 }
405 
406 void QgsFeatureFilterModel::reloadCurrentFeature()
407 {
408  mShouldReloadCurrentFeature = true;
409  mReloadTimer.start();
410 }
411 
412 void QgsFeatureFilterModel::setExtraIdentifierValueUnguarded( const QVariant &extraIdentifierValue )
413 {
414  const QVector<Entry> entries = mEntries;
415 
416  int index = 0;
417  for ( const Entry &entry : entries )
418  {
419  if ( entry.identifierValue == extraIdentifierValue
420  && entry.identifierValue.isNull() == extraIdentifierValue.isNull()
421  && entry.identifierValue.isValid() == extraIdentifierValue.isValid() )
422  {
423  setExtraIdentifierValueIndex( index );
424  break;
425  }
426 
427  index++;
428  }
429 
430  // Value not found in current entries
431  if ( mExtraIdentifierValueIndex != index )
432  {
433  beginInsertRows( QModelIndex(), 0, 0 );
434  if ( extraIdentifierValue.isNull() )
435  mEntries.prepend( Entry( QVariant( QVariant::Int ), QStringLiteral( "%1" ).arg( tr( "NULL" ) ), QgsFeature() ) );
436  else
437  mEntries.prepend( Entry( extraIdentifierValue, QStringLiteral( "(%1)" ).arg( extraIdentifierValue.toString() ), QgsFeature() ) );
438  endInsertRows();
439 
440  setExtraIdentifierValueIndex( 0, true );
441 
442  reloadCurrentFeature();
443  }
444 }
445 
446 QgsConditionalStyle QgsFeatureFilterModel::featureStyle( const QgsFeature &feature ) const
447 {
448  if ( !mSourceLayer )
449  return QgsConditionalStyle();
450 
451  QgsVectorLayer *layer = mSourceLayer;
452  QgsFeatureId fid = feature.id();
453  mExpressionContext.setFeature( feature );
454 
455  auto styles = QgsConditionalStyle::matchingConditionalStyles( layer->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
456 
457  if ( mDisplayExpression.referencedColumns().count() == 1 )
458  {
459  // Style specific for this field
460  QString fieldName = *mDisplayExpression.referencedColumns().constBegin();
461  const auto allStyles = layer->conditionalStyles()->fieldStyles( fieldName );
462  const auto matchingFieldStyles = QgsConditionalStyle::matchingConditionalStyles( allStyles, feature.attribute( fieldName ), mExpressionContext );
463 
464  styles += matchingFieldStyles;
465  }
466 
467  QgsConditionalStyle style;
468  style = QgsConditionalStyle::compressStyles( styles );
469  mEntryStylesMap.insert( fid, style );
470 
471  return style;
472 }
473 
475 {
476  return mAllowNull;
477 }
478 
480 {
481  if ( mAllowNull == allowNull )
482  return;
483 
484  mAllowNull = allowNull;
485  emit allowNullChanged();
486 
487  reload();
488 }
489 
491 {
492  return mExtraValueDoesNotExist;
493 }
494 
495 void QgsFeatureFilterModel::setExtraValueDoesNotExist( bool extraValueDoesNotExist )
496 {
497  if ( mExtraValueDoesNotExist == extraValueDoesNotExist )
498  return;
499 
500  mExtraValueDoesNotExist = extraValueDoesNotExist;
502 }
503 
505 {
506  return mExtraIdentifierValueIndex;
507 }
508 
510 {
511  return mIdentifierField;
512 }
513 
515 {
516  if ( mIdentifierField == identifierField )
517  return;
518 
519  mIdentifierField = identifierField;
520  emit identifierFieldChanged();
521 }
522 
523 void QgsFeatureFilterModel::reload()
524 {
525  mReloadTimer.start();
526 }
527 
529 {
530  return mExtraIdentifierValue;
531 }
532 
534 {
535  if ( extraIdentifierValue == mExtraIdentifierValue && extraIdentifierValue.isNull() == mExtraIdentifierValue.isNull() && mExtraIdentifierValue.isValid() )
536  return;
537 
538  if ( mIsSettingExtraIdentifierValue )
539  return;
540 
541  mIsSettingExtraIdentifierValue = true;
542 
543  mExtraIdentifierValue = extraIdentifierValue;
544 
545  setExtraIdentifierValueUnguarded( extraIdentifierValue );
546 
547  mIsSettingExtraIdentifierValue = false;
548 
550 }
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:71
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.
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.
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:62
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...
Conditional styling for a rule.
QString displayExpression() const
The display expression will be used for.
QgsFields fields() const override
Returns the list of fields of this layer.
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 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.
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.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
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.
qint64 QgsFeatureId
Definition: qgsfeature.h:37
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.
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:255
void extraValueDoesNotExistChanged()
Flag indicating that the extraIdentifierValue does not exist in the data.
QString rule() const
The condition rule set for the style.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
void displayExpressionChanged()
The display expression will be used for.